Welcome to PocketSprite SDK’s documentation!

Getting Started

So, you want to actually develop applications for the PocketSprite? Great choice! (and I’m not only saying that because I just spent ages typing up the SDK documentation.) This document will allow you to get started with this.

Hardware

First of all, you will need hardware to develop for. Obviously, you want to have a real PocketSprite to run the applications on. If you haven’t yet, go buy one! If you don’t have one yet, or don’t want to use the one you have for development, you can also use an ESP32 devboard, a cheap LCD and an audio amp plus speaker to build a ‘fake’ PocketSprite you can develop on.

Software

The PocketSprite uses ESP-IDF, as well as the toolchain for the Xtensa processor that is in the ESP32 chip that forms the brains of the small console. While ESP-IDF is included (as a submodule) in the PocketSprite SDK, the toolchain needs some setting up; please refer to the ESP-IDF documentation to install it.

Note that while ESP-IDF supports Windows, Linux and Mac as development platforms, the PocketSprite SDK for now only supports Linux (and work on Mac with Brew, see the note below). Some users have reported success with using Windows Subsystem for Linux.

After you have installed esp-idf using the linked instructions, get the PocketSprite SDK components. Note that the SDK still uses the old ‘8bkc’ name as an identifier:

cd ~/esp
git clone --recursive https://github.com/PocketSprite/8bkc-sdk.git

Now, to let your PocketSprite projects know where the SDK lives, you’ll need to add a line to your shell profile, similar to what you needed to do for the ESP-IDF installation. In case you use Bash as a shell and you have a file called .bash_profile in your home directory, edit that file, otherwise open up the .profile file in your home directory. Regardless of the file you now are editing, add a line indicating where the 8bkc-sdk can be found:

export POCKETSPRITE_PATH=~/esp/8bkc-sdk

You can now log out and log in again and check if the change took by doing printenv POCKETSPRITE_PATH. It should return the path you entered earlier.

Finally, the PocketSprite SDK contains some utilities running on your computer that need to be compiled. The SDK will automatically take care of this, but it needs to have a compiler (gcc) and the development packages of some libraries (libgd, libxml2, imagemagick) installed. Please make sure these are available. For instance, under Debian/Ubuntu, you would do:

apt-get install gcc libgd-dev libxml2-dev imagemagick

to install them.

For Mac, gcc should already come with XCode and need to install the dependencies using Brew instead:

brew install libgd libxml2 imagemagick

You can get Brew here.

PocketSprite Hardware

PocketSprite specifications

The PocketSprite contains: - An ESP32 with:

  • 2x240MHz 32-bit processor
  • 520KiB of RAM
  • WiFi
  • BT
  • 16 MiB of QSPI-connected flash memory
  • An 96x64 OLED screen (with 8 rows covered on both sides, emulating a power LED and making for an 80x64 ‘real’ screen)
  • A LiIon battery (150mAh)
  • A D-pad (with mechanical buttons for up, down, left, right)
  • An A, a B, a select, a start and a power button
  • A small amplifier and (mono) speaker
  • A micro-USB port for charging the LiIon battery (no data connection)

Development PocketSprite hardware

If you want to develop PocketSprite apps or games but do not have access to a real PocketSprite or have other reasons for not wanting to use the real hardware, the SDK has an option to allow compilation for ‘fake’ PocketSprite hardware. For this, you need:

  • An ESP32 devboard with at least 4MiB of flash
  • A display with an ILI9341 or ST7789V controller

(The Espressif ESP32-Wrover-Kit devboards have displays like this.)

Optionally, but highly recommended are:

  • A small amplifier plus speaker to amplify the sound signal on GPIO26
  • A PlayStation 1/2/PSX controller

To enable ‘fake’ PocketSprite mode, in a project, run make menuconfig. Here, under PocketSprite hardware config -> Hardware to compile binary for, select the applicable option. For custom boards, the GPIOs the LCD and the controller are connected to are configurable, although it is highly recommended to go with the defaults if you can. Analog audio will always be emitted on GPIO26; this cannot be changed.

If you do not have an PlayStation controller to connect, the serial console can also be used to emulate the PocketSprite keys: in make monitor (or in any other serial port terminal emulator), you can use the arrow or JIKL keys to emulate the D-pad, AS to emulate the A and B button, ZX for the start and select button and P for the power button. Due to the nature of terminal emulators, the serial port button emulation comes with a few issues, however: multiple keys pressed together aren’t registered and ‘held’ keys may not register as such but as a series of repeated taps instead. It is advised to use a PlayStation controller or real hardware if precise button input is important.

To flash a PocketSprite configured for the ‘fake’ PocketSprite into the devboard, just run make flash. The application will start immediately after reset.

Limitations

The ‘fake’ mode has some things that are not implemented:

  • No appfs, so no filesystem to load external files from. The app binary itself is all you have access to. This is done to make the development system fit into the 4MiB flash that most devboards come with.
  • No chooser. The devboard will boot immediately into your app. HAL API calls to return to chooser will result in a program abort.
  • No powerdown/poweroff features. HAL API calls for this will result in a program abort
  • No battery monitoring features. Battery voltage will always be reported as 3.6V.

(Note: These limitiations also mean that the GB/SMS emulators can be cross-compiled to a devboard, but they will not be able to load any ROMs due to lack of an AppFs filesystem and as such will most likely crash.)

PocketSprite Hardware

PocketSprite Hardware Abstraction Layer

The PocketSprite has a fair amount of hardware, driven by various internal peripherals and GPIOs. It also has some internal communication with the bootloader to boot the various applications that may be installed on it. The HAL manages all this and more. For compatibility and upgradability, it is highly recommended to use the HAL to do low-level things instead of poking the hardware directly.

Functions

int kchal_get_hw_ver()

Get PocketSprite hardware version.

Return
  • -1 if hardware is not a PocketSprite
  • 1 for initial revision of hardware

void kchal_init_sdk(int flags)

Initialize SDK.

This is used to initialize the SDK software, allowing SDK functions to be called. It does not initialize any of the hardware.

Warning
For normal use, it is not advised to call this function. Use kchal_init() instead.
Parameters
  • flags: Bitmap of KCHAL_INIT_* flags

void kchal_init_hw(int flags)

Initialize PocketSprite hardware.

This is used to initialize the PocketSprite hardware, allowing hardware-related SDK functions to work.

Warning
For normal use, it is not advised to call this function. Use kchal_init() instead.
Parameters
  • flags: Bitmap of KCHAL_INIT_* flags

void kchal_init()

Initialize PocketSprite SDK and hardware.

Before calling any of the other SDK functions, this function should normally be called. It essentially combines kchal_init_sdk() and kchal_init(). Please call this early in your apps main function.

void kchal_init_custom(int flags)

Initialize PocketSprite SDK and hardware in a custom fashion.

Same as kchal_init but takes flags to get custom behaviour

Parameters
  • flags: Bitmap of KCHAL_INIT_* flags

uint32_t kchal_get_keys()

Get keys pressed.

Return
A bitmap of the keys pressed; essentially an OR of all KC_BTN_* values of the keys pressed.

void kchal_wait_keys_released()

Wait until all buttons currently pressed are released.

Note
Any keys pressed while this routine is running will not be monitored.

void kchal_send_fb(const uint16_t *fb)

Send framebuffer to display.

Note
The kchal_fbval_rgb inline function provides an easy way to calculate pixel values starting from 8-bit RGB values.
Parameters
  • fb: Pointer to KC_SCREEN_W*KC_SCREEN_H (80x64) 16-bit values. The 16-bit values are defined in 5-6-5 RGB format (RRRRRGGG:GGGBBBBB), BUT with the two bytes swapped, so GGGBBBBB:RRRRRGGG.

void kchal_send_fb_partial(const uint16_t *fb, int x, int y, int h, int w)

Send partial framebuffer to display.

Parameters
  • fb: Pointer to h*w 16-bit values. Values are formatted as kchal_send_fb() describes.
  • x: Starting horizontal position

void kchal_sound_start(int rate, int buffsize)

Start sound subsystem.

This starts up the I2S peripheral and configures the DAC for sound output. It allows you to push a single sound stream to the speaker using kchal_sound_push.

Parameters
  • rate: Sample rate, in Hz.
  • buffsize: Buffer size, in bytes. Internally, I2S uses 32-bit samples, so the actual buffer is ((buffsize/4)/rate) seconds.

void kchal_sound_push(uint8_t *buf, int len)

Send samples to the sound subsystem.

This function is used to send a number of new samples to the sound subsystem. If the sound buffer is full, this function will block until it is empty enough to store the provided samples and only return then.

Before the data passed here is output to the speaker, it is attenuated by the volume setting first.

Parameters
  • buf: Samples, as unsigned bytes. (DC = 128)
  • len: Lenght, in bytes, of the sample buffer buf

void kchal_sound_stop()

Stop/deinitialize the audio subsystem.

void kchal_sound_mute(int doMute)

Mute audio.

Silences the speakers. Use this when e.g. a game is paused and no data is fed into the audio subsystem anymore using kchal_sound_push.

Parameters
  • doMute: 1 to mute, 0 to unmute again.

void kchal_power_down()

Power down the PocketSprite.

Does the little crt-powering-off animation and puts the PocketSprite in deep sleep, to wake again when the power button is pressed.

When the power button is pressed again, your application will re-start from scratch, with all but the RTC memory wiped clean. If you want to do any savestate saving, do it before you call this.

int kchal_get_chg_status()

Get the charging status of the PocketSprite.

Return
One of the KC_CHG_* values

void kchal_set_volume(uint8_t new_volume)

Set the volume.

Note: The volume set here is stored in nvram as well and will be kept over a restart or app change.

Parameters
  • Volume0: is muted, 255 is full volume.

uint8_t kchal_get_volume()

Get the current volume.

Return
Volume, 0 is muted, 255 is full volume.

void kchal_set_brightness(int brightness)

Set the brightness of the screen.

Parameters
  • Brightness.: 0 is lowest, 255 is full highest

uint8_t kchal_get_brightness()

Set the brightness of the screen.

Return
Brightness. 0 is lowest, 255 is full highest

void kchal_exit_to_chooser()

Exit to chooser.

Stops the current app and reboots into the chooser menu, where the user can e.g. select a different app to run. This function does not return.

void kchal_set_new_app(int fd)

Set new app to start after deep sleep.

Set a new app to start after deep sleep is over.

This function is mostly used for chooser use (to start a new, chosen app) but can also be used by individual apps. Call kchal_boot_into_new_app to actually reboot into the new app.

Parameters
  • fd: The AppFS fd of the app to start

int kchal_get_new_app()

Get app set by set_new_app.

Return
new app as set by kchal_set_new_app, possibly from the chooser or another app

void kchal_boot_into_new_app()

Boot into the app chosen using kchal_boot_into_new_app.

This function does not return.

int kchal_get_bat_mv()

Get the current battery voltage in millivolt.

Return
Current battery voltage

int kchal_get_bat_pct()

Get the estimated battery full-ness, in percent.

Return
Battery full-ness, 0-100. 0 is empty, 100 is entirely full.

void kchal_cal_adc()

Re-calibrate the ADC when the battery is fully charged.

Used during ATE. Please do not call from apps.

nvs_handle kchal_get_app_nvsh()

Get the NVS handle for the current app.

To facilitate storing things like high-scores, preferences etc, it is possible to use the NVS subsystem of esp-idf. To stop namespace clashes, the PocketSprite SDK allocates a namespace for each installed app. Use this call to get a handle to that namespace; feel free to save whatever fragment you like into it. Please do not use more than a few K here: the NVS partition is shared between all apps. For larger storage, please write to an AppFs file.

static uint16_t kchal_fbval_rgb(uint8_t r, uint8_t g, uint8_t b)

Calculate a value for a pixel in the OLED framebuffer from a triplet of 8-bit RGB values.

Parameters
  • r: Red value (0-255)
  • g: Green value (0-255)
  • b: Blue value (0-255)

Macros

KC_BTN_RIGHT

‘Right’ on D-pad is pressed

KC_BTN_LEFT

‘Left’ on D-pad is pressed

KC_BTN_UP

‘Up’ on D-pad is pressed

KC_BTN_DOWN

‘Down’ on D-pad is pressed

KC_BTN_START

‘Start’ button is pressed

KC_BTN_SELECT

‘Select’ button is pressed

KC_BTN_A

‘A’ button is pressed

KC_BTN_B

‘B’ button is pressed

KC_BTN_POWER

‘Power’ button is pressed

KC_BTN_POWER_LONG

‘Power’ button is pressed and held longer than 1.5 seconds

KC_CHG_NOCHARGER

No charger attached, running from battery

KC_CHG_CHARGING

Charger attached, internal battery is charging

KC_CHG_FULL

Charger attached, battery is fully charged.

KC_SCREEN_W

Screen width, excluding bezel area

KC_SCREEN_H

Screen height

KCHAL_INIT_NO_STDOUT_HDL

Do not install custom stdout handler

uGUI adapter

The PocketSprite SDK comes with a version of uGUI. This is a library to easily render text and GUI elements to a screen on an embedded device. The SDK comes with some adapter logic to easily integrate this library into a PocketSprite application.

Functions

void kcugui_cls()

Clear the uGUI framebuffer to black.

void kcugui_flush()

Flush the uGUI framebuffer to the OLED screen.

void kcugui_init()

Initialize uGUI and this connector.

void kcugui_deinit()

De-initialize uGUI, release associated memory.

uint16_t *kcugui_get_fb()

Get pointer to framebuffer used by uGUI.

For this, uGUI needs to be initialized using kcugui_init. The framebuffer returned will be KC_SCREEN_W*KC_SCREEN_H (normally 80*64) 16-bit words.

Return
Pointer to framebuffer

static uint16_t kchal_ugui_rgb(uint8_t r, uint8_t g, uint8_t b)

Convert an RGB value into a color usable by uGUI.

Return
uGUI color value
Parameters
  • r: Red value (0-255)
  • g: Green value (0-255)
  • b: Blue value (0-255)

PocketSprite Sound Mixer

The PocketSprite SDK comes with a sound mixer and decoder, capable of decoding mono wave files and music modules in .mod/.s3m/.xm format. It can decode and play multiple of these files simultaneously, mixing them using different volumes.

(ToDo: detail memory use, note that .xm is more intensive than .mod/.s3m)

Functions

int sndmixer_init(int no_channels, int samplerate)

Initialize the sound mixer.

Note
This function internally calls kchal_sound_start, there is no need to do this in your program if you use this function to initialize the sound mixer.
Parameters
  • no_channels: Amount if sounds to be able to be played simultaneously.
  • samplerate: Sample rate to mix all sources to

int sndmixer_queue_wav(const void *wav_start, const void *wav_end, int evictable)

Queue the data of a .wav file to be played.

This queues a sound to be played. It will not be actually played until sndmixer_play is called.

Return
The ID of the queued sound, for use with the other functions.
Parameters
  • wav_start: Start of the wav-file data
  • wav_end: End of the wav-file data
  • evictable: If true, if all audio channels are filled and a new sound is queued, this sound can be stopped to make room for the new sound.

int sndmixer_queue_mod(const void *mod_start, const void *mod_end)

Queue the data of a .mod/.xm/.s3m file to be played.

This queues a piece of tracked music to be played. It will not be actually played until sndmixer_play is called.

Return
The ID of the queued sound, for use with the other functions.
Parameters
  • wav_start: Start of the filedata
  • wav_end: End of the filedata

void sndmixer_set_loop(int id, int loop)

Set or unset a sound to looping mode.

Warning
This may not be implemented yet. TODO: remove this warning when implemented.
Parameters
  • id: ID of the sound, obtained when queueing it
  • loop: If true, the sound will loop back to the beginning when it ends.

void sndmixer_set_volume(int id, int volume)

Set volume of a sound.

A queued sound will always start off with a volume setting of 255 (max volume). This call can be used to adjust the volume of the sound at any time afterwards.

Parameters
  • id: ID of the sound, obtained when queueing it
  • volume: New volume, between 0 (muted) and 255 (full sound).

void sndmixer_play(int id)

Play a sound.

When a sound is queued, it is not playing yet. Use this call to start playback. You can also use this call to resume a sound paused by sndmixer_pause or sndmixer_pause_all.

Parameters
  • id: ID of the sound, obtained when queueing it

void sndmixer_pause(int id)

Pause a sound.

Stops playback of the sound. The sound can be resumed with sndmixer_play().

Parameters
  • id: ID of the sound, obtained when queueing it

void sndmixer_stop(int id)

Stop a sound, free the sound source and channel it used.

Stops playback of the sound and frees all associated structures.

Parameters
  • id: ID of the sound, obtained when queueing it

void sndmixer_pause_all()

Pause all playing sounds.

This can be used when e.g. the game is paused. Sounds can be individually un-paused afterwards and new sounds can still be queued and played, given enough free/evictable channels.

void sndmixer_resume_all()

Resume all paused sounds.

This can be used to undo a sndmixer_pause_all() call.

Structures

struct sndmixer_source_t

Structure describing a sound source.

Public Members

int (*init_source)(const void *data_start, const void *data_end, int req_sample_rate, void **ctx)

Initialize the sound source. Returns size of data returned per call of fill_buffer.

int (*get_sample_rate)(void *ctx)

Get the actual sample rate at which the source returns data

int (*fill_buffer)(void *ctx, int8_t *buffer)

Decode a bufferful of data. Returns 0 when file ended or something went wrong. Returns amount of bytes in buffer (normally what init_source returned) otherwise.

void (*deinit_source)(void *ctx)

Destroy source, free resources

PocketSprite TileGFX tile renderer

The PocketSprite SDK comes with a powerful tile rendered. This tile rendered is capable of rapidly rendering maps of 8x8 graphics tiles to the screen, including effects like transparency and fading. The SDK has integration with the files generated by Tiled, a powerful and open-source tilemap editor, allowing the tilemaps generated in that program to be embedded in and rendered from PocketSprite applications.

Creating tiles and tilemaps

Essentially, tiles are small (in this case, 8x8 pixels) images or parts of bigger images. These tiles can be placed next to each other on a bigger canvas, not unlike floor tiles can be placed on a floor in order to fill the entire floor. The bigger canvas is called a ‘tile map’ and can be however big you want it to be; for instance, an entire level in your platformer game.

The advantage of this approach is that the tile map uses a much smaller amount of memory when compared to having the entire map as an image: because tiles can be re-used (for example, the blue of the sky can be the same tile repeated over and over again) and shared between maps, a tile map can use a much smaller amount of memory than the graphics it represents. Using tiles also has some other advantages: for instance, tiles can be animated, leading to an easy way to add individual elements of animation to a tilemap.

Tilegfx, the PocketSprite tile renderer, is optimized to work with maps generated by Tiled and the SDK contains a conversion program to convert its native .tsx/.tmx files directly into .c/.h files which can then be included in a PocketSprite process.

In order to create a tiled tile map that works with tilegfx:

  • First, create a .png containing all the tiles in an 8x8 pixel grid in a program of your choosing. Be sure to save the png as true color (RGB), not indexed. You can use one color (#FF00FF is regularily used for this) to indicate a transparent pixel.
  • Open tiled, create a new tileset (File -> New -> New tileset). Select the png created earlier. It’s important to select a tile height and width of 8px by 8px here. Also set the transparency color here, if any.
  • If so desired, modify the tile set (add animations, …)
  • Create the tilemap (File -> New -> New map). Select Orthogonal for orientation, CSV for tile layer format, Right Down for rendering order and set the tile size to 8px x 8px. You can freely choose the map size here.
  • Create your map. Make sure the layer you’re working in has a sane name, as that will be part of the name of the resulting tilegfx_map_t struct containing the layer data.
  • Save the map and tileset somewhere in your project

The conversion from Tiled format to C file can now be automated. In a component.mk file in your PocketSprite project, add a line similar to this:

$(eval $(call ConvertTiles,level1.tmx,levelgfx))

This will convert a tilemap file called level1.tmx and its associated tileset to a file called levelgfx.c as soon as you build the project. The levelgfx.c is automatically compiled and linked with the project. A file called levelgfx.h is also created; you can use this to refer to the tilemap data from other C source files.

Note: because of quirks of the build system, this method breaks the automatic detection of other C files. This means you need to specify them manually. For instance, if the component also contains an app_main.c and a enemy.c file, a minimal component.mk would look like this:

$(eval $(call ConvertTiles,level1.tmx,levelgfx))
COMPONENT_OBJS += app_main.o enemy.o

Rendering tile maps

With tile maps defined, you can now render them using the API defined down below. The way tilegfx works is that it only has one render target, namely the screen buffer. The screen buffer can either be the same height and width of the OLED screen of the PocketSprite, or it can be twice as large and scaled down when sent to the OLED screen. The second option uses more memory, but by using subpixel scaling it can actually make the displayed graphics look like it has a higher resolution than the screen natively has.

It is possible to render multiple layers over eachother: if a tile map has transparency defined, the transparent regions will allow the earlier rendered graphics to shine through.

Functions

int tilegfx_init(int double_res, int hz)

Initialize tilegfx system.

Allocates the OLED framebuffer and the infrastructure for the virtual VBlank.

Return
True if succesful, false if out of memory.
Parameters
  • double_res: Set to 0 to get a framebuffer in the native (80x64) resolution; set to 1 to get a 160x128 framebuffer that is then subpixel-aware scaled to the OLED.
  • hz: Set the ‘refresh rate’, as in the tilegfx_flush call is throttled to a maximum of this amount of calls per second.

void tilegfx_flush()

Render the OLED framebuffer to the actual OLED display.

This shows the result of all tilegfx calls on the PocketSprite display. This call may block for a while in order for it to never be called at more than ‘refresh rate’ passed to tilegfx_init().

void tilegfx_deinit()

De-initialize tilegfx.

This frees the OLED-buffer and all other resources allocated when initializing tilegfx.

void tilegfx_tile_map_render(const tilegfx_map_t *tiles, int offx, int offy, const tilegfx_rect_t *dest)

Render a tilemap to screen.

This renders the content of a tilemap to the OLED framebuffer. It takes care of transparency and clips the regions that fall outside of the destination rectangle. The tilemap is rendered in a repeating way, so it ‘wraps around’ when displaying bits outside its actual height and/or width.

Parameters
  • tiles: The tilemap to render
  • offx: X-offset in the tilemap. The tilemap is started with (offx, offy) in the top right corner if the destionation rectangle.
  • offy: Y-offset in the tilemap.
  • dest: Rectangle, in the coordinates of the OLED framebuffer, to limit rendering to. If this parameter is NULL, the tilemap is rendered to the entire framebuffer.

void tilegfx_fade(uint8_t r, uint8_t g, uint8_t b, uint8_t pct)

‘Fade’ the framebuffer to a certain color.

This effectively sets every pixel in the OLED framebuffer to a color that is (pct/255)’th the original color that was there, 1-(pct/255)’th the color indicated by the RGB values. A pct value of 256 does not affect the OLED framebuffer, a pct value of 0 clears it entirely to the color given in r,g,b.

Parameters
  • r: Red component of fade color
  • g: Green component of fade color
  • b: Blue component of fade color
  • pct: Fade amount (0-256)

tilegfx_map_t *tilegfx_create_tilemap(int w, int h, const tilegfx_tileset_t *tiles)

Create an empty tilemap in RAM.

This can be used to generate a tilemap that is in RAM and this modifiable from the code

Return
The map (with all tiles set to 0xffff/transparent), or NULL if out of memory
Parameters
  • w: Width, in tiles, of the map
  • h: Height, in tiles, of the map
  • tiles: Tileset the map will use

tilegfx_map_t *tilegfx_dup_tilemap(const tilegfx_map_t *orig)

Create an editable copy of a tilemap.

Use this to duplicate a tilemap located into flash into one located into RAM.

Return
Duplicated tilemap, or NULL if out of memory
Parameters
  • orig: Tilemap to duplicate

void tilegfx_destroy_tilemap(tilegfx_map_t *map)

Free a tilemap created with tilegfx_create_tilemap or tilegfx_dup_tilemap.

Parameters
  • map: Map to free

static void tilegfx_set_tile(tilegfx_map_t *map, int x, int y, uint16_t tile)

Set tile position in RAM-allocated map to the specific tile in its associated tileset.

Parameters
  • map: Tilemap to modify
  • x: X-position of tile to change
  • y: Y-position of tile to change
  • tile: Tile index to change to (or 0xffff for completely transparent)

static uint16_t tilegfx_get_tile(const tilegfx_map_t *map, int x, int y)

Get tile in specified tile position of tilemap.

Return
tile Tile index in tileset (or 0xffff for completely transparent)
Parameters
  • map: Tilemap to read
  • x: X-position of tile
  • y: Y-position of tile

uint16_t *tilegfx_get_fb()

Get internal framebuffer.

This returns a pointer to the internal framebuffer memory where tilegfx renders everything. You can use this to manually poke pixels into memory, or to get access to the raw pixels after some tiles have been rendered. Note that in double_res mode, after rendering tilesets this framebuffer is 160x128 pixels in size, but after calling tilegfx_flush the contents will be resized to 80x64!

Pixels here are in big-endian RGB565 format (same as what’s sent to the OLED).

Structures

struct tilegfx_anim_frame_t

Structure describing one frame of animation.

Public Members

uint16_t delay_ms

Delay for one frame. On the 0th frame of a seq, this indicates total duration.

uint16_t tile

Tile index for frame. 0xffff on 0th frame of a seq.

struct tilegfx_tileset_t

Structure describing a set of tiles, usable in a tilemap.

Public Members

int trans_col

transparent color, or -1 if none

const uint16_t *anim_offsets

Array of offsets into the animation frames array. Indexed by tile index. If a tile is animated, it will have an offset number in this array at its index. That offset is 0xffff if no animation.

const uint16_t tile[]

Raw tile data. Each tile is 64 16-bit words worth of graphics data.

struct tilegfx_map_t

Structure describing a tilemap.

Public Members

int h

Height of the tilemap, in tiles

int w

Width of the tilemap, in tiles

const tilegfx_tileset_t *gfx

Pointer to the tileset used in the map

const uint16_t tiles[]

Array of the tiles in the map. Values can be 0xffff for no tile.

struct tilegfx_rect_t

Structure describing a rectangle.

Public Members

int x

Position of left side of rectangle

int y

Position of top of rectangle

int w

Width of rectangle

int h

Height of rectangle

AppFS

The PocketSprite includes a flash filesystem that allows storing multiple ESP32 applications, as well as other files, in the ESP32 flash. This filesystem is called appfs. Because this filesystem has to be compatible with the way the ESP32 loads applications and maps memory, it is somewhat different from other filesystems: while files have names and sizes as you’d expect, accessing them is more akin to accessing the raw flash underneath it and the file access functions mirror the ESP-IDF flash functions more than they look like standard POSIX file access methods.

An appfs can only be stored in a partition in the SPI flash the ESP32 boots from, and thus has a maximum size of 16MiB. The file system has a sector size of 64KiB which lines up with the 64KiB pages of the ESP32 MMU. This, however, also implies that any file stored on the file system occupies at least 64KiB of flash. With this in mind, please store your data in one large file instead of many smaller ones whenever possible

Some quirks of this approach are:
  • Files cannot be expanded or shrunk. The filesize has to be known on creation and cannot be changed afterwards.
  • A write to a file can only reset bits (1->0). In order to overwrite existing data, you need to make sure the region you write in is erased, and if not, erase it first. Note that erasing can only happen on a 4K block.
  • The appfs on the PocketSprite is 15.3 MiB in size. With a minimum file size of 64K, this means at maximum there can be 245 files stored in the flash.

Note: PocketSprite software not running on PocketSprite hardware does not have an AppFS filesystem available.

Functions

esp_err_t appfsInit(int type, int subtype)

Initialize the appfs code and mount the appfs partition.

Run this before using any of the other AppFs APIs

Return
ESP_OK if all OK, an error from the underlying partition or flash code otherwise.
Parameters
  • type: Partition type. Normally you’d pass APPFS_PART_TYPE here.
  • subtype: Partition subtype. Normally you’d pass APPFS_PART_SUBTYPE here.

int appfsExists(const char *filename)

Check if a file with the given filename exists.

Return
1 if a file with a name which exactly matches filename exists; 0 otherwise.
Parameters
  • filename: Filename to check

bool appfsFdValid(int fd)

Check if a file descriptor is valid.

Because file descriptors are integers which are more-or-less valid over multiple sessions, they can be stored in non-volatile memory and re-used later. When doing this, a sanity check to see if the fd still points at something valid may be useful. This function provides that sanity check.

Return
True if fd points to a valid file, false otherwise.
Parameters
  • fd: File descriptor to check

appfs_handle_t appfsOpen(const char *filename)

Open a file on a mounted appfs.

Return
The filedescriptor if succesful, APPFS_INVALID_FD if not.
Parameters
  • filename: Filename of the file to open

void appfsClose(appfs_handle_t handle)

Close a file on a mounted appfs.

Note
In the current appfs implementation, this is a no-op. This may change in the future, however.
Parameters
  • handle: File descriptor to close

esp_err_t appfsDeleteFile(const char *filename)

Delete a file on the appfs.

Return
ESP_OK if file successfully deleted, an error otherwise.
Parameters
  • filename: Name of the file to delete

esp_err_t appfsCreateFile(const char *filename, size_t size, appfs_handle_t *handle)

Create a new file on the appfs.

Initially, the file will have random contents consisting of whatever used the sectors of flash it occupies earlier. Note that this function also opens the file and returns a file descriptor to it if succesful; no need for a separate appfsOpen call.

Return
ESP_OK if file successfully deleted, an error otherwise.
Parameters
  • filename: Name of the file to be created
  • size: Size of the file, in bytes
  • handle: Pointer to an appfs_handle_t which will store the file descriptor of the created file

esp_err_t appfsMmap(appfs_handle_t fd, size_t offset, size_t len, const void **out_ptr, spi_flash_mmap_memory_t memory, spi_flash_mmap_handle_t *out_handle)

Map a file into memory.

This maps a (portion of a) file into memory, where you can access it as if it was an array of bytes in RAM. This uses the MMU and flash cache of the ESP32 to accomplish this effect. The memory is read-only; trying to write to it will cause an exception.

Return
ESP_OK if file successfully deleted, an error otherwise.
Parameters
  • fd: File descriptor of the file to map.
  • offset: Offset into the file where the map starts
  • len: Lenght of the map
  • out_ptr: Pointer to a const void* variable where, if successful, a pointer to the memory is stored.
  • memory: One of SPI_FLASH_MMAP_DATA or SPI_FLASH_MMAP_INST, where the former does a map to data memory and the latter a map to instruction memory. You’d normally use the first option.
  • out_handle: Pointer to a spi_flash_mmap_handle_t variable. This variable is needed to later free the map again.

void appfsMunmap(spi_flash_mmap_handle_t handle)

Unmap a previously mmap’ped file.

This unmaps a region previously mapped with appfsMmap

Parameters
  • handle: Handle obtained in the previous appfsMmap call

esp_err_t appfsErase(appfs_handle_t fd, size_t start, size_t len)

Erase a portion of an appfs file.

This sets all bits in the region to be erased to 1, so an appfsWrite can reset selected bits to 0 again.

Return
ESP_OK if file successfully deleted, an error otherwise.
Parameters
  • fd: File descriptor of file to erase in.
  • start: Start offset of file portion to be erased. Must be aligned to 4KiB.
  • len: Length of file portion to be erased. Must be a multiple of 4KiB.

esp_err_t appfsWrite(appfs_handle_t fd, size_t start, uint8_t *buf, size_t len)

Write to a file in appfs.

Note: Because this maps directly to a write of the underlying flash memory, this call is only able to reset bits in the written area from 1 to 0. If you want to change bits from 0 to 1, call appfsErase on the area to be written before calling this function. This function will return success even if the data in flash is not the same as the data in the buffer due to bits being 1 in the buffer but 0 on flash.

If the above paragraph is confusing, just remember to erase a region before you write to it.

Return
ESP_OK if write was successful, an error otherwise.
Parameters
  • fd: File descriptor of file to write to
  • start: Offset into file to start writing
  • buf: Buffer of bytes to write
  • len: Length, in bytes, of data to be written

esp_err_t appfsRead(appfs_handle_t fd, size_t start, void *buf, size_t len)

Read a portion of a file in appfs.

This function reads len bytes of file data, starting from offset start, into the buffer buf. Note that if you do many reads, it usually is more efficient to map the file into memory and read from it that way instead.

Return
ESP_OK if read was successful, an error otherwise.
Parameters
  • fd: File descriptor of the file
  • start: Offset in the file to start reading from
  • buf: Buffer to contain the read data
  • len: Length, in bytes, of the data to read

esp_err_t appfsRename(const char *from, const char *to)

Atomically rename a file.

This atomically renames a file. If a file with the target name already exists, it will be deleted. This action is done atomically, so at any point in time, either the original or the new file will fully exist under the target name.

Return
ESP_OK if rename was successful, an error otherwise.
Parameters
  • from: Original name of file
  • to: Target name of file

void appfsEntryInfo(appfs_handle_t fd, const char **name, int *size)

Get file information.

Given a file descriptor, this returns the name and size of the file. The file descriptor needs to be valid for this to work.

Parameters
  • fd: File descriptor
  • name: Pointer to a char pointer. This will be pointed at the filename in memory. There is no need to free the pointer memory afterwards. Pointer memory is valid while the file exists / is not deleted. Can be NULL if name information is not wanted.
  • size: Pointer to an int where the size of the file will be written, or NULL if this information is not wanted.

appfs_handle_t appfsNextEntry(appfs_handle_t fd)

brief Get the next entry in the appfs.

This function can be used to list all the files existing in the appfs. Pass it APPFS_INVALID_FD when calling it for the first time to receive the first file descriptor. Pass it the result of the previous call to get the next file descriptor. When this function returns APPFS_INVALID_FD, all files have been enumerated. You can use appfsEntryInfo() to get the file name and size associated with the returned file descriptors

Return
Next file descriptor, or APPFS_INVALID_FD if all files have been enumerated
Parameters
  • fd: File descriptor returned by previous call, or APPFS_INVALID_FD to get the first file descriptor

esp_err_t appfsGetCurrentApp(appfs_handle_t *ret_app)

Get file descriptor of currently running app.

Return
ESP_OK on success, an error when e.g. the currently running code isn’t located in appfs.
Parameters
  • ret_app: Pointer to variable to hold the file descriptor

size_t appfsGetFreeMem()

Get amount of free space in appfs partition.

Return
amount of free space, in bytes

void appfsDump()

Debugging function: dump current appfs state.

Prints state of the appfs to stdout.

Macros

APPFS_PART_TYPE
APPFS_PART_SUBTYPE
APPFS_INVALID_FD

Type Definitions

typedef int appfs_handle_t

Indices and tables