This one explores the frame buffer proof-of-concept and defines the font I'll be using for text output.
The GameBoy uses tiled graphics, where the 160x144 pixels screen is divided into a 20x18 2-D tile map of bytes that reference one of 256, 8x8 pixel tiles. This would appear to prevent a full pixel mapped image to be displayed, because there are 360 tile map locations and only 256 tiles they can reference.
However, it's possible to switch the set of tiles used by the Game Boy to provide access to another 128 tiles (0..127) with the higher numbered tiles (128..255) being the same as in the first set. In addition, the set of tiles can be switched on the fly by generating an interrupt just at the end of the first set. This means we can use a total of 384 tiles, more than enough to uniquely cover the screen.
To support a simple frame buffer; all the tiles needed to be in order and I needed a demo image. The easiest way to do this when bringing up a new system IMHO, is to fill the frame buffer with successive values: 0..255. Then you can see them all as binary patterns. I made the first set of tiles black binary patterns and the second set of 128 tiles light grey patterns. The only other change I made was to use the first set of tiles rather than the second set (in memory order), so my code to turn on the LCD looked like:
So, initially I could see that it was using the same set of tiles twice, because they were all dark. Then I'd know the interrupt worked because the remaining 360-256=104 tiles would look light. As a general principle that works for both this kind of game development and embedded programming, incremental changes are far more preferable, because errors are caught quickly and early.
Xors can be used to swap between any two values, not merely to turn a bit on and off, because of the identity: b^a^b=a. So, if a=numberX and we want to swap with numberY then xor numberX^numberY will generate numberX^numberX^numberY=numberY the first time around and numberY^numberX^numberY=numberX the second time around. This saves on an if.. else.. control flow. The GameBoy then generates the display:
This is actually a fairly small change to the firmware, but took quite a bit of work. I wanted to use a 4x8 font, so that I could display 40x18 characters on the screen instead of 20x18 (a 4x6 font could achieve 40x24, but they're harder to read and it'd involve crossing tile boundaries). The font looked like this:
GameBoy tiles are 8x8, but 2 bits per pixel. The pixels aren't paired, instead there's 8 light-grey pixels followed by 8 dark-grey pixels (and setting the same bits on both bytes gives black).This means I would need to unpack my font bytes, but I don't need to expand 4-bits into 8-bits. So, for black on white text that's easy, I just repeat both bytes for every scan on the tile.
I've ended up using font like this (or a 6x6 font) several times, and I always end up having to convert it to hex, so I thought I'd just publish it here:
CharSet:
db 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x00 ; !
db 0x00, 0x50, 0x52, 0x07, 0x02, 0x07, 0x02, 0x00 ;"#
db 0x00, 0x25, 0x71, 0x42, 0x72, 0x14, 0x75, 0x20 ;$%
db 0x00, 0x21, 0x52, 0x20, 0x60, 0x50, 0x60, 0x00 ;&'
db 0x00, 0x14, 0x22, 0x22, 0x22, 0x22, 0x14, 0x00 ;()
db 0x00, 0x00, 0x52, 0x22, 0x77, 0x22, 0x52, 0x00 ;*+
db 0x00, 0x00, 0x00, 0x00, 0x07, 0x20, 0x20, 0x40 ;,-
db 0x00, 0x01, 0x01, 0x02, 0x02, 0x64, 0x64, 0x00 ;.
db 0x00, 0x22, 0x56, 0x52, 0x52, 0x52, 0x27, 0x00 ;01
db 0x00, 0x22, 0x55, 0x12, 0x21, 0x45, 0x72, 0x00 ;23
db 0x00, 0x57, 0x54, 0x76, 0x11, 0x15, 0x12, 0x00 ;45
db 0x00, 0x37, 0x41, 0x61, 0x52, 0x54, 0x24, 0x00 ;67
db 0x00, 0x22, 0x55, 0x25, 0x53, 0x55, 0x22, 0x00 ;89
db 0x00, 0x00, 0x02, 0x20, 0x02, 0x22, 0x04, 0x00 ;:;
db 0x00, 0x00, 0x10, 0x27, 0x40, 0x27, 0x10, 0x00 ;<=
db 0x00, 0x02, 0x45, 0x21, 0x12, 0x20, 0x42, 0x00 ;>?
db 0x00, 0x62, 0x95, 0xb7, 0xb5, 0x85, 0x65, 0x00 ;@A
db 0x00, 0x62, 0x55, 0x64, 0x54, 0x55, 0x62, 0x00 ;BC
db 0x00, 0x67, 0x54, 0x56, 0x54, 0x54, 0x67, 0x00 ;DE
db 0x00, 0x72, 0x45, 0x74, 0x47, 0x45, 0x42, 0x00 ;FG
db 0x00, 0x57, 0x52, 0x72, 0x52, 0x52, 0x57, 0x00 ;HI
db 0x00, 0x35, 0x15, 0x16, 0x15, 0x55, 0x25, 0x00 ;JK
db 0x00, 0x45, 0x47, 0x47, 0x45, 0x45, 0x75, 0x00 ;LM
db 0x00, 0x52, 0x55, 0x75, 0x75, 0x55, 0x52, 0x00 ;NO
db 0x00, 0x62, 0x55, 0x55, 0x67, 0x47, 0x43, 0x00 ;PQ
db 0x00, 0x62, 0x55, 0x52, 0x61, 0x55, 0x52, 0x00 ;RS
db 0x00, 0x75, 0x25, 0x25, 0x25, 0x25, 0x22, 0x00 ;TU
db 0x00, 0x55, 0x55, 0x55, 0x57, 0x27, 0x25, 0x00 ;VW
db 0x00, 0x55, 0x55, 0x25, 0x52, 0x52, 0x52, 0x00 ;XY
db 0x00, 0x77, 0x14, 0x24, 0x24, 0x44, 0x77, 0x00 ;Z[
db 0x00, 0x47, 0x41, 0x21, 0x21, 0x11, 0x17, 0x00 ;\]
db 0x00, 0x20, 0x50, 0x00, 0x00, 0x00, 0x07, 0x00 ;^_
db 0x00, 0x20, 0x56, 0x41, 0x63, 0x45, 0x73, 0x00 ;£a
db 0x00, 0x40, 0x42, 0x65, 0x54, 0x55, 0x62, 0x00 ;bc
db 0x00, 0x10, 0x12, 0x35, 0x56, 0x54, 0x23, 0x00 ;de
db 0x00, 0x20, 0x52, 0x45, 0x65, 0x43, 0x45, 0x02 ;fg
db 0x00, 0x42, 0x40, 0x66, 0x52, 0x52, 0x57, 0x00 ;hi
db 0x00, 0x14, 0x04, 0x35, 0x16, 0x15, 0x55, 0x20 ;jk
db 0x00, 0x40, 0x45, 0x47, 0x47, 0x55, 0x25, 0x00 ;lm
db 0x00, 0x00, 0x62, 0x55, 0x55, 0x55, 0x52, 0x00 ;no
db 0x00, 0x00, 0x63, 0x55, 0x55, 0x63, 0x41, 0x41 ;pq
db 0x00, 0x00, 0x63, 0x54, 0x42, 0x41, 0x46, 0x00 ;rs
db 0x00, 0x40, 0x75, 0x45, 0x45, 0x55, 0x22, 0x00 ;tu
db 0x00, 0x00, 0x55, 0x55, 0x57, 0x27, 0x25, 0x00 ;vw
db 0x00, 0x00, 0x55, 0x55, 0x23, 0x51, 0x55, 0x02 ;xy
db 0x00, 0x00, 0x71, 0x12, 0x26, 0x42, 0x71, 0x00 ;z{
db 0x00, 0x20, 0x24, 0x22, 0x23, 0x22, 0x24, 0x00 ;|}
db 0x00, 0x06, 0xaf, 0x59, 0x0b, 0x09, 0x0f, 0x06 ;~©
As you can tell, every 8 bytes contains a pair of characters. It's just the printable 96 characters, so they take 384 bytes in total. The display then shows...
Starting with a basic Tile-mapped display demo, I added a line match interrupt which was triggered on scans 96 and 144, inverting the tile set each time. I set the TileMap to point to successive tiles (mod 256). The real GameBoy Tile Map is 32x32, so in reality I had to skip 12 bytes in the TileMap after each 20 byte row. I hand-converted a 4x8 font I had lying around: two characters will fit in each half of an 8x8 bitmap. That maps nicely onto the GameBoy Tiles (I just repeat each byte), but for real text display I'll have to properly mask off the correct half of each character and combine it with the existing background.