Skip to content
makapuf edited this page May 8, 2017 · 2 revisions

Discovering the Kernel

first, ensure your toolkit is all OK and that you can upload a premade game, or that the emulator is present.

The main hw functions are exposed through drivers, named kernels. They are very simple hardware interfaces, and can be completed by engines which will give you tiles, sprites, ... all of those extra features are not in a kernel. note : currently there's only one kernel, which can drive the gamepad, video 640x480@60fps, and sound.

The kernel interface is also implemented by other kernels : SDL based for PC under linux, test kernel for headless build tests or bitbox micro.

You will have to compile the kernel within your game (it's tiny, made of a few .c / .h files, depending on how much you want to support eg sdio, usb), configured fromthe makefile of your game. The main interface to the kernel is the kernel.h file.

There will be mostly two main "threads" (those are not really threads but interrupt handlers) : a graphics line thread

The kernel will run the main loop, your game will not have access to it. If you want to, just redefine main()

the kernel API exposes the following main elements in kernel.h (as a C library) :

  • a game_init call back :

Initialize everything on your game, setup variables ...

  • a game_frame callback do what is needed every frame, like getting user input, moving sprites x/y/frame , .... , using the global integer "frame".

Note that if you redefine main(), you can do whatever you'd like. Just wait for the next frame when you need to with the wait_vsync(number of frames) function.

There, we will be reading the joystick and updating two variables x and y.

  • a global variable "gamepad1" the gamepad is simply an uint16, with 1 bit for each button. There are macros to check if a key is pressed or not.

  • a void graph_line (void) callback

  • uint16_t draw_buffer[641]

  • line : an int

The callback is called before each line is blit on the screen. You should draw each line (everytime) by blitting your screen line to the buffer, being given a line number (from 0 to 479). You have ~5k cycles to blit your graphics. draw_buffer is the buffer of 640 uint16 pixels to draw the line on, and an int to know which line we will be drawing (from 0 to 479).

You can blit however you want, do what you want in 5k cycles per line : from aligned memsets (2 pixels at a time, word by word transfers, very quick) to full antialiased, rotozommed, alpha blurred sprites (very expensive - tiny sprites !) : you do it & tune it (that's part of the fun) !

Of course, building a library / engine that you can reuse & tune from game to game is useful, but sometimes, the ad-hoc just blit it (tm) engine is simpler !

NB : the last elements of the buffer (non visible ones), namely draw_buffer[640] and next, should be zero, but the buffer itself is 1024 wide, so you can overdraw a bit if it's easier on you tile and then reset to black.

Hello, bitbox kernel !

Now, we're going to examine the test_kernel program. It will just provide a sample display and let us check if the controller is well plugged.

Feel free to experiment with it by modifying it.

We're importing the bitbox kernel and common libraries.

#include <kernel.h>
#include <string.h> // memset

Then, we're declaring two variables x and y to track the cursor position

volatile int x,y; // x and y should be volatile since the vga thread must see the changes to x and y // which are running in the main thread

The game_init will be simple as we won't use it !

void game_init()  {}

In the game_frame callback, we're going to manage the cursor movement, based on the PRESSED macros.

void game_frame()
{
    if (PRESSED(up) && y>-240) y--;
    if (PRESSED(down) && y<240) y++;
    if (PRESSED(left) && x>-320) x--;
    if (PRESSED(right) && x<320) x++;
} // simple, uh ?

and now that our scene is ready (ie we have x and y :) we will display it : that's for the game_line callback which will be outputting VGA lines.

void graph_line()
// called from VGA kernel
{   
    // clear the line with a repeating red/black gradient
    for (int i=0;i<640;i++) draw_buffer[i] = line%0x0f; 

    draw_buffer[640]=0; // force pixel after screen to black.

    // first oblique line (behind)
    for (int i=0;i<128;i++)
        draw_buffer[line+i] = (i/8)<<4;

    // square "effect"
    if ((line-frame*2)%128 <64)
        for (int i=200;i<200+256;i++)
            draw_buffer[i]|=0x777; // you can modify the buffer

    // second oblique line (front)
    for (int i=0;i<64;i++)
        draw_buffer[640-line-i] = (i/4)<<8;

    // display gamepad state as an inverse video point
    if (line==200+y)
    {
        draw_buffer[320+x]^=0xfff;
    }
}

please compile it with make and seethe result on a SDL window or upload it on the bitbox : your first program is finished !

remember however that you don't have the memory for a full frame, so you can't "draw" something in vram, nor remember last frame : you CANNOT have a framerate lower than 60fps.

The whole program is available on git

And now, what next ? Well, let's program a full game ! Also, if you want to go deeper in the documentation, please peruse STM32f4 reference manual, datasheet, ARM ref manual and this post by example http://www.theresistornetwork.com/2013/09/arm-bare-metal-programming.html

Clone this wiki locally