We're going over the course of this tutorial to program and build a simple program for the bitbox console on ubuntu. I'm trying it on a 13.04 Ubuntu, so adapt accordingly.
For windows, there is a small guide on the blog, you can use is in parallel with this page.
Compiler setup
We're going to get a build environment to compile to ARM Cortex M4 bare metal, including the compiler, the libraries and a command line debugger.
First, we need to be sure that the standard gcc / make / ... are present for your computer. We will not use gcc to target the board, bu to build the emulator. However, make will be used for any of those.
Then, we're going to install a cross compilator, namely a version of GCC running of the PC but targeted at our console, with no OS or libs to use. We're going to use the excellent work done by the gcc arm embedded team, which makes the process a breeze : see https://launchpad.net/gcc-arm-embedded
For ubuntu,
First, install some development tools (open a terminal and input)
sudo apt-get install build-essentials
sudo apt-get update
sudo apt-get install gcc-arm-none-eabi
Testing the compilation
get the sample bitbox SDK on the website, untar it on your source directory
go to the kernel_test directory and then type make
it won't work, of course, because your makefile includes another : the bitbox.mk
so you'll need to point an environment variable named BITBOX to your base directory.
This allows changing kernels, referencing utility scripts and having several projects with the same kernel base. So please just add
export BITBOX=path/to/bitbox/files (just below the /lib)
to your .bashrc.
The main files are under /lib , and each application has its own subdirectory.
Congratulations, you just built your first bitbox application !
Uploading
To uploading to the board, you can use a stlink debug interface (main option now) or directly to the board using USB dfu protocol. Or you can just put the .bin files (and the icon!) on the root of your sd card.Uploading using SWD debug interface
Hardware setup
uploading on bitbox1 .. of which there is only one |
Connect the 4-pin SWD interface to the SWD interface of bitbox and the power from the Discovery to your board. Beware that the connexions on the bitbox are (as rev 0.1) gnd-IO-Clk-Vcc, while the connectors on the Discovery are Vdd, CLK, Gnd, IO.
uploading using bitbox2 |
Software setup
- first, download stlink from texane (https://github.com/texane/stlink), compile it & install it.- use st-flash to flash the binary file to SRAM or Flash
example commands (see the whole tutorial on flashing there)
st-flash write 0x8000000 mygame.bin (for flash)
There is GUI option also in the recent versions of stlink
Uploading directly to the board
- boot on the bootloader : You can look at the bootloader sections on this blog !- use dfu-util on the binary
Debugging
Optionally, if you have a st-link v2 compatible board, you will be able to connect and inspect your dev board live.You'll be able to see it all on the device it self, it's 1e100 times more easier than "LED debugging" ...
Step by step running, hardware breakpoints ? Check.
SRAM Memory value access ? check.
Flash value reading ? Check.
Hardware counter values & configuration ? check.
Hardware DMA configuration & values read ? check.
Pins values ? check
You get the idea : since everything is mapped in memory and you have access to read memory.
run st-util from texane (see uploading to the board) in a separate terminal which will be serving on port 4242 locally
st-util
then run gdb & connect to the remote
gdb
> target ext remote :4242
(I like to have a "make debug" ready in my command files to check I'm debugging the right binary)
Don't forget to *load* your binary to the device
load
If you just modified your source code, you can do
make
load
run
don't forget to build your binaries including debug symbols with -g
then position breakpoints, disp values (including using stm32f4xx.h types direct from memory, ex : p *(GPIO_Type*) 0x4002000) to see all GPIOA registers
Links
Those links will help you understand how to setup such a toolchain (hint, the right google query is "toolchain linux stm32")
http://www.wolinlabs.com/blog/linux.stm32.discovery.gcc.html
http://fun-tech.se/stm32/
http://www.triplespark.net/elec/pdev/arm/stm32.html (beware they're using JTAG )
The emulator
To run the emulator under linux, you'll need build-essential and libsdl1.2-devel
The emulator also exposes the same functions defined in kernel.h with the emulator.
Which means that the same game can be compiled with the emulator or on the board - up to a certain point ...
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.
There are several kernel config that you can use, check the kconf.h file in $(BITBOX)/lib. To use it, just define a variable in your makefile.
You will have to compile the kernel within your game (it's tiny, made of a few .c / .h files)
The main interface to the kernel in the kernel.h file.
The kernel will run the main loop, your game will not have access to it.
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".
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 game_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 game_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 see the 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 : [...]
Well, this can be a bit tedious to rewrite all of this for simple sprites, so you can either use the ultra raw version, or the engine (which will be improved but ... )
...
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
Please ! leave a comment about your feeling concerning those long tutorials in the comments : are they too easy ? boring ?
Hi there! Great stuff you've got here!
ReplyDeleteIf I was to implement this using my stm32f4-discovery board, is there anything I would have to consider regarding your recently posted schematics?
It's kind of late and all but I can't seem to find that SDK link?! :)
well, I'm not sure how the embedded peripherals on the discovery will interact with the pins that need to be connected, but the STM405 and 407 are sufficiently close that a port might be possible .. good luck !
ReplyDeleteFor the "SDK" (pompous word ..) you can check the github repo now.
Thanks! Soldering at the vga dac right now...
ReplyDeleteWell the only port that doesn't seem to hampered with peripherals is "E", we'll see how that turns out.
By the way, have you considered supporting usb gamepads through the usb host interface?
Wow, that's quick !
ReplyDeleteBe careful that hsync needs to be driven by a timer.
USB HID would be very cool. Getting rid of MiniDin6, supporting keyboard/mouse/gamepads ... but I didn't have time to make / test the driver for it ! Do you have any experience on HID host hacking?
Well the soldering was quick, but I didn't get to test it until today. Tested waha's jupiter&beyond demo, it was awesome!
ReplyDeleteMy HID host experience is limited to PICs, but I found some examples to dig into:
https://github.com/MrBlueXav/USB_host_HID_demo
http://www.youtube.com/watch?v=2sR_Ko15N2s
Anyway, I've been reading the STMuzebox thread in the uzebox forum and saw that some wishes for stereo sound and ethernet. For stereo I guess you could just add the other DAC-output to the other sound-channel. With a stm32f407 ethernet is also within reach, though it makes the kernel kinda complicated.
I don't know, I really like the simplicity of your current design.
Yeah the demo is quite cool indeed.
ReplyDeleteI also digged more into the HID spec / demos and found that it might indeed be feasible to kick the minidin to replace it with a microUSB OTG, for keyboards, cool controllers or mice. It would be more work, but much easier once the foirmware is working on a few devices to reuse a controller where you don't have to re-solder a minidin . Finding a ps2 keyboard might prove difficult also.
That would mean a second usb also just for powering the thing. Uploading games with USB dfu bootloader.will still be possible if we go for device/host OTG (or even making my own mass storage bootloader, but this isn't a priority). I then discovered that the usb port I've been using was NOT the one used by the bootloader (uurgh). But I'm pretty convinced that micro usb otg+second usb (either just power or usb fs) is the way to go. Also a second button would be nice for boot1.
Considering stereo, I think it won't be so useful, nor difficult. (doubling the lowpass filters, but that's not toobad. Still not sure, but why not now.)
For Ethernet, I'm not fan of the lack of phy on the chip which would imply more costs (407+extra phy) + huge plug for just link to the network. linking 2 bitboxes can be done with the extra usart pins.
For networking, a wifi chip (TI has a nice one I think ?) or a simple enc26xx for ethernet could be used on the expansion port and not much more expensive.
Expansion port sounds like a good idea! With a SPI-interface and some extra pins (PC0-7 makes half-a-port for example) on it, it could be very useful.
ReplyDeleteIf you made the OTG_HS-port host mode only (and standard A-female for convenience), you could free up the SPI2-interface. Make the dfu-port OTG, and with an external power supply you could support two pads without need of a hub.
Saw that you were thinking of 15-bit colors, does that mean you're stretching for a 100-pin chip instead of the 64-pin? :)
Hi, I've put some thought in it and I don't think SPI2 will be usable without SCK conflicting on portB10. Having full size host only is a good idea !
ReplyDeleteThis thread is becoming too interesting for a comment thread. I opened a google group (https://groups.google.com/forum/#!forum/bitbox-console), will you please continue the discussion there ?
I've applied for joining the group. :)
ReplyDeleteIn the data sheet SPI2_SCK is also available on PB13, and SPI2_NSS on PB12 (same as OTG_HS_ID, not needed if this port is host only).