|return to ChibiAkumas.com||Choose a Color Scheme:||Dark|
| Lesson P1 - Bitmap Functions on
The BBC has a variety of graphics modes - at this time, we'll just be looking at the 320x200 4 color mode - Mode 1... this screen mode actually goes up to 256 lines, but we'll make it smaller to save memory
Screen data is memory mapped... but on the BBC it's in a slightly odd format... lets take a look!
|The screen layout
on the BBC is not the same as used by most other systems (though is
used on the C64)...
The screen is split up into 8 pixel tall strips... and these are stored in memory Y first... then X
Lets imagine we write bytes to consecutive memory addresses (displayed as 0-641 in the diagram to the right)...
when we write to the first 8 pixels, they go DOWN the screen... but the 9th pixel will go back up to the top of the strip
This will continue untill the far right of the screen... then the next byte will be the far left of the next 8 pixel tall strip.
tutorials will currently only cover Mode 1 - which offers 320x200
4-color bitmap graphics - at this time the other screen modes won't be
The reason for this is that the screen resolution and color depth makes it good for games - but it uses a lot of memory - so it's not suitable for 16k machines...
We may cover other screen modes later - but for now we're just going to cover this one!
|IThis tutuorial uses Mode 1 (320x256 @ 4 color)... however depending on your requirements you may prefer to use Mode 5 (160x256 @ 4 color) or Mode 4 (320x256 @ 2 color)... it will just depend on your graphical requirements and how much memory you can spare!|
|We're going to need
to define the settings we want to use for our screen layout and
The values shown here will set up a screen 320x200 or 256x192, and set a 4 color palette with a blue background
The Color definitions are odd!... each Byte is spilt onto two nibbles which we'll call $SC... S is the screen nibble (Logical color), and C is the resulting color (physical color) - selected from the EOR column in the chart shown above...
We should set all 4 of the C nibbles to the same color for a 'normal screen... if we do not - opposite columns will have different colors, or pixels with different colored neighbors will be strange - try different settings to see what happens!
Note registers 12 and 13... these define the memory address of the screen - however for reasons unknown, you have to divide the address by 8... we've mapped the 256 pixel wide to $5000... or the 320 pixel wide one to $4130 - so the screen goes up to the $8000 area (the top address on a 32k machine)
|When it comes to
setting up the screen we need to send the bytes to the hardware...
When we want to set CRTC registers, we send the register number we want to set to address $FE00, and we send the data for that register to $FE01
This will set up the Screen position.
Next we need to set the Screen mode - we select Mode 1 by sending $D8 to address $FE20
Finally, we need to set up the colors, we do this by sending 16 bytes to $FE21 to define the color layout onscreen.
doesn't look like the Horizontal and Vertocal sync registers have any
effect on BeebEM ... it seems like maybe these aren't supported by the
The values here should work, but the author of these tutorials doesn't own a BBC so it's not been tested on real hardware!... feel free to donate one if you're not happy with that!
|We're going to draw
our Chibiko character to the screen! - we can convert a bitmap into the
correct format for the BBC screen.
We can just write data to video ram to draw, but we need a way of working out the correct location to draw to... we'll define a function called GetCursorPos
This will take an X,Y position (in bytes) in the X and Y registers and will set a Zero Page pair Z_DE to the destination screen address...
We can then write out bytes to that address to draw our bitmap!
function is pretty long, but the concept is pretty simple...
First we take the Xpos and we multiply it by 8 - this is because there's 8 lines of bytes between each column, due to the way the screen is structured...
On a 320 pixel wide screen, each strip of 8 lines is $280 bytes wide... on the 256 pixel wide screen it's $200 (which is much easier to calculate!)
We ignore the bottom 3 bits of the Y line - to get the 'strip' number - then multiply this by either $280 or $200 to get the offset - we do this by bitshifting the Y pos into the $02 and $08 (in the case of 320 screens) , and save these in zero page Z_DE
Finally we add the start address of the screen... the settings we're using map it to the top of the memory area - so we either add $5000 (for the 256 pixel screen) or $4180 (for the 320 pixel screen)
|If we're within an 8 line strip, we can move down a
line just by INCing DE
Really this is just here for compatibility with other systems!
|Note: Because the BBC works in 8 pixel tall strips, the GetScreenPos function ignores the bottom 3 bits of the Y line - if you need that functionality, you can add it to the function - of course, this would make the function slightly slower, but if it's what you need, then go for it!|
|We're going to use Z_HL as the source data of our bitmap|
|When it comes to drawing the bitmap, we're going to do
it in 8 pixel tall 'Strips' to match the screen layout...
At the start of each strip, we're going to use GetScreenPos to work out the position to write to... we're then going to write bytes from Z_HL (the bitmap) to Z_DE (the screen) using Y as an offset count...
Once we've written all the bytes within this strip, we'll move Y down 8 lines, update Z_HL to move past the data we've just copied
We now reapeat the procedure until the bitmap is drawn!
This routine uses Y as a counter for bytes within the current strip...
that means this function will only work for a bitmap upto 32 bytes (128
pixels) wide... if you need more - you're going to have to do the work
What? you want it ALL doing for you? - yeah good luck with that!
| Lesson P2 - Bitmap Functions on
the Atari 800 / 5200
The Atari 5200 and 800 are capable of 320x200 in 2 colors, or 160x200 in 4 colors - and the screen layout is a typical bitmap - so it should be easy to use...
Unfortunately, setting it up is hard - we need to define a 'Display list' which configures the screen layout, and the A5200 and A800 have graphics hardware at different memory locations!... Lets learn how to make it behave!
|Atari 5200||Atari 800|
|Name||Description||Address A80||Address A52|
|COLPF0||Color/brightness of setcolor 0||$D016||$C016|
|COLPF1||color/brightness of setcolor 1||$D017||$C017|
|COLPF2||color/brightness of setcolor 2||$D018||$C018|
|COLPF3||color/brightness of setcolor 3||$D019||$C019|
|COLBK||color/brightness of setcolor 4||$D01A||$C01A|
|DMACTL||Direct Memory access control (DMA)||$D400||$D400|
|DLISTL||display list pointer low byte||$D402||$D402|
|DLISTH||display list pointer high byte||$D403||$D403|
|$x0-$xF||Screen mode change|
|$70||8 Blank lines|
|$4x $BB $AA||Start Screen mode x at address $AABB|
|$41 $BB $AA||Wait for Vblank, and restart display list at $AABB|
|Here's the display
list we'll be using!
We're defining the definition at the end of our rom, so that it won't go over a 1k boundary...
We're using the 'smode' symbol for our screen mode - this will support mode E or F - as they use the same amount of screen ram...
Notice, half way through the definition we're using $40 to restart the screen ram - we have to do this because our 7K screen MUST go over a 4K boundary, and the screen will wrap unless we manually step into the $3000 address...
|When we want to start the screen, we need to load the
display list address into $D402 (DLISTH/L)
We then need to start the screen processing the list, by writing %00100010 to $D400 (DMACTL)
|This template will work for screen modes E or F - but if you want to use different modes, or you want to change mode midscreen, you'll want to write a new one... just make sure you follow the rules, and you should be OK!|
|We'll be using 2
and 4 color mode on the Atari...
4 color mode is pretty straightforward, but 2 color mode is a bit odd...
In 2 color mode only the BRIGHTNESS of the 2nd color is used - they'll both be the same color as the background!
on the Atari will differ depending on if you're using PAL or NTSC...
there also seems to be an inconsistency in the way Atari800win and Jum52 work with 2 color mode - but this may be a bug in the emulator!
|We're going to calculate the screen position - each line of the screen is 40 bytes, and our screen starts at $2060... so our calculation for the screen position is $2060+(40*Ypos)+Xpos||
|When we call this
function, register X will be the Xpos, and Y will be the Ypos
We start by moving X into zeropage address z_e
Next we'll move Y into z_b, and set z_d and A to zero
We're now going to shift 3 bits from z_b to A... we'll add this to z_de
Then we'll shift another 2 bits from z_b to A... we'll add this to z_de as well... we've now effectively added Y*40
Now we add the screen base... this completes the address calculation
|As the lines are directly beneath each other in memory,
we just add 40 to move down a line
When we want to move down a line, we just add 40 to the current position
|Doing the 'Multiplication' by bitshifts is
confusing to look at but it's faster...
Using a loop with Y and repeatedly calling GetNextLine would work and be simpler, but it would be much slower!
|We're going to use
z_hl to point to the source bitmap.
We'll use GetScreenPos to set z_de to the destination
When we want to get a bitmap onto the screen we'll use Y as an offset to copy each line from the source to the destination....
We'll then move down a screen line by using GetNextLine...
and we'll add Y to HL to move the source data for the next line...
we'll repeat the procedure until we've don all the lines.
| Lesson P3 - Bitmap Functions on
the Apple II
The Apple 2 has an odd screen! with a 280x192 screen size, and a screen that is neither quite 2 color or 4 color where 7 pixels are contained within each byte, it's strange indeed...
Lets try and figure it out!
|Colors on the Apple II are effectively an 'Artifact' of
certain combinations of Off (0) and On (1) pixels will appear colored... this is known as Composite Artifact colors...
Unlike pretty much every system in existance, 8 bits of a byte draw 7 pixels!.... the top bit is a 'Color bit'... selecting 'Palette 0 or 1
The remaining 7 bits are the 7 pixels of bitmap data... because each line is 40 bytes wide, the Apple II screen is a rather odd resolution of 280×192
|Because of these artifacts, a '2 color' bitmap will
show colors depending on the combination of the pixels...
My Akusprite editor offers a half horizontal resolution mode, where the 4 colors will be converted to the correct bit combinations
important to understand there isn't a separate 2 and 4 color mode, it's
just the combination of pixels that will appear different colors...
AkuSprite Editor halves horizontal resolution when exporting 4 colors... but If you're super smart ,you could have both the colors, and better resolution, by cleverly laying out the pixels!
for Screen Mode
2 is split into 3 chunks,also, every 8 lines we effectively 'reset' our
high memory address and add $80
Pixels in Each line are in normal Left->Right format, however remeber 7 pixels are defined by each byte, with 1 bit defining the color palette.
We can calculate the address of the start of a line by splitting the bits of the Y line number...
On the Apple II the graphics screen can start at $2000 or $4000 ... we'll put it at $4000 so it's out the way of our program code!
Address= Base+ (AA*$0028) + (BBB*$0080) + (CC*$0400) + XPOS
|We need to 'set' a
variety of ports to enable the screen in the mode we need...
We just need to read or write to or from ports $C050,$C052,$C055 and $C057 to set up the screen mode we need...
Just accessing the ports has the effect of setting up the hardware!
|To init the screen, we just need to INIT the screen, displaying Fullscreen graphics... setting HighRes mode, and Setting Page2 - so the screen memory is $4000-$5FFF|
|It seems any data written to, or read from
these ports will lave the effect of causing the setting change...
we're using LDA here, but STA will work too, and any value can be written, and the effect will be the same... weird eh?
|When we want to
draw on the screen, we'll need to calculate the correct memory position
to write the data to...
We'll do this with 'GetScreenPos'... it'll use the X and Y regster to specify a co-ordinate, and calculates the equivalent memory location in the zeropage pair defined by z_de
We split up the Y-pos, and multiply each part according to the foruma below...
Address= Base+ (AA*$0028) + (BBB*$0080) + (CC*$0400) + XPOS
|Within an 8 line 'block'... We can move down a line by just adding $0400... but we'll have to recalculate the X,Y co-ordinate after that.|
|When it comes to
drawing the bitmap to the screen, we just need to use GetScreenPos to
calculate the destination in z_de...
Then we copy lines of data from the source bitmap in z_hl...
For the first 8 lines, we can use GetNextLine to calculate the next screen position, but after that we need to call GetScreenPos again to recalculate the new position
| Lesson P4 - Bitmap Functions on
the Atari Lynx
Unlike most of our systems The Lynx has a 16 color Bitmap screen - effectively we can just write data to it - one nibble per color to ram, and it will appear on screen!
We do need to do a bit of setup first to get it working!
|The 160x102 video display is created by 8k of normal
memory, and we can just write data into it to set pixels...
This is no different to a machine like the BBC - but what is quite odd is the Hardware Sprites of the Lynx... a normal hardware sprite system would be in a separate layer which is drawn, but on the Lynx that's not the case...
The Lynx's hardware sprite processor (Suzy) reads compressed sprite data from normal CPU ram, and renders the sprite data back into normal CPU ram!... the sprite CPU is EXTREMELY fast - so this is pretty much instant - and means the Lynx is capable of quickly drawing a HUGE number of hardware sprites!
We're not going to use sprites today, but it's important to understand that the Lynx screen is 100% bitmap based!
|We're going to use
a variety of hardware addresses to set up the screen... though most are
actually for sprite settings...
One VERY important thing is that when setting 16 bit HL pairs, we MUST set the Low value first... as writing to the Low value will reset the High value
|The lynx uses 8160
bytes of ram, and the screen memory
can be positioned anywhere within the address space... in
fact we would often want two 8k screen buffers - one visible, and one
In these examples we'll just use one , so we'll draw straight to the visible screen...
We also set up the screen 'offsets' - these are used for sprite 'clipping' (where sprites are partially offscreen, and we'll need them later!.... at the same time we'll set up a few other sprite defaults, though we don't really need them today,
Finally we'll set up some default colors so we can see our screen and text.
|We're loading the screen-buffer at $C000 - near the top of the memory map - but you can load it anywhere you want - also remember, despite the confusion of hardware sprites, we can treat the Lynx screen as a simple bitmap... Grime 6502 did this - it never used hardware sprites - and in fact, for a 6502 the Lynx is very fast anyway - so you may never need hardware sprites!|
|Each line in the
screen is 80
bytes, so to calculate a screen pos we just multiply Y*80 and add X to
get our offset from the base...
To effect the multiplication, we'll shift the Y value into the two positions that make up 80 in binary, and add the values to the resulting address
|This function takes
an Xpos in bytes and, Ypos in lines in the XY registers.
When it comes to calculating the address, we use A as the top byte, and z_c as the low one...
We load in Y - and shift two bits into z_c... we then store the pair in z_d and z_e
Now we shift another two bits... and add to z_d and z_e.... along with the base address of the screen ($C000)
Finally we add the X pos, and any carry as required - we've now got the memory address of the required byte in z_de
|We can move down a line by just adding 80 to z_de|
|It's easy to draw a
bitmap to the screen, we can just load the bytes
from memory (pointed to by z_hl), get the destination with the
GetScreenPos function we just defined, and then copy bytes to z_de
After each line, we need to move down a line, and start drawing again - until all the lines of the bitmap are drawn
|The bitmap will be drawn onscreen at the position we specify|
its' small screen, the Lynx is one of the best systems we'll be looking
at! It has 16 colors - but unlike a tile based system, we can plot data
in memory easily!...
Of course if you like the tile based way of doing things, the SNES or PC-Engine may suit you better... Though technically they aren't 6502 based.
| Lesson P5 - Bitmap Functions on
the PC Engine (TurboGrafx-16)
The PC Engine is a Tile/Sprite based system capable of 16 colors per tile...
We're going to learn how to use these tiles to draw our 'Chibiko' character onto the screen
|The PC Engine graphics hardware
has 64k of ram... but it's designed for
128k... it's controlled by a set of registers... we have 3 hardware
ports we use to control the hardware... these are usually memory mapped
to $0000 ... but we also have special commands to quickly write fixed
values to the graphics system
We ALWAYS write data to the graphics system registers in HL byte pairs... Little Endian, so low byte first.
To write data to the memory we set the address we want to write to with MAWR... but please note , we're only setting the last 16 of the 17 bits... for example, if we set MAWR to $3FFF (%11111111111111), the actual memory address will be %111111111111110
The effect is, that there are 2 bytes at VRAM $0000... and two bytes at VRAM $0001 ... and these bytes DO NOT OVERLAP!
PC Engine documentation will often show writes to the video hardware at
$0000,$0002 and $0003 - but VASM will confuse these with Zero page
addressing, so we'll write to $0100,$0102 and $0103 instead
The ports repeat every 4 bytes, so this has the same effect, and gets around the limitation of the assembler.
|00||MAWR||Memory Address Write|
|01||MARR||Memory Address Read|
|02||VRR/VWR||Vram Data Write / Vram Data Read|
|05||CR||Control||- - - IW IW DR TE TE BB SB EX EX IE IE IE IE (BB= Background on) (SB=Sprites on)|
|06||RCR||Scanning Line Detection|
|07||BXR||BGX Scroll||------XX XXXXXXXX
|08||BYR||BGY Scroll||-------Y YYYYYYYY|
|09||MWR||Memory Access Width||- - - - - - - - CM SCR SCR SCR SM SM WV WV|
|0E||BCR||Vertical Display End Position|
|0F||DCR||Block Transfer Control|
|10||SOUR||Block Transfer Source Address|
|11||DESR||Block Transfer Destination Address|
|12||LENR||Block Transfer Length|
|13||SATB||VRAM-SATB Block Transfer Source|
|Memory in the Vram is not entirely fixed in purpose,
this means you can have weird effects, like using the same memory area
for your Tilemaps - and tile definitions (Patterns)... this is totally
useless, as one will corrupt the other,
but we have to understand it's possible to understand the memory... as stated before, each address has 2 bytes...
A Pattern (tile definition) is 32 bytes in size (4 bitplanes, 8 lines)... and because each memory address in the Vram map contains 2 bytes... the pattern will take up 16 memory addresses... this means tile 0 starts at $0000... and tile 1 is at $0010
NOW... the TileMap has to be at $0000 .. and it takes AT LEAST $0400 (it's minimum size is 32x32, and each definition takes 2 bytes)... for ease, it's probably easiest to start your pattern definitions at no 256 (memory address $1000)
|To start the screen
we need to access Register 5 (the control register) we do this by using
ST0 with the parameter #5
We need to turn on the background tilemap with Bit 7, we'll also turn on sprites with Bit 6 (We'll need them later)... we write this using ST1 - we also use ST2 #0, as the registers take 2 bytes
Next we need to define our tilemap with Reg 9... we're going to define a 32x32 tilemap for our background.
We need to initialize the position of the tilemap with registers 7 & 8... for some reason 0,0 will not be the top corner unless we set the Ypos to 248!
Last we'll set the background color to blue by setting entry 0 in the VCE to a blue color... By default all the colors are white, and we wouldn't be able to see anything!
setting up a scrolling area of 32x32, but the visible screen is 32x29 -
if we were making a game with a scrolling background, it would make
sense to have a scrolling area of 64x32 or more,
It just depends what you're doing, Grime 6502 has no scrolling, so this layout was fine!
|We're going to draw
our 'Chibiko' character to the screen... the character bitmap is 48x48
, we're going to have to split it into 8x8 tiles to get it onto our
The tiles are defined in Vram from $1000-$7FFF - this range will have tile numbers 256+ , but the first few will be used by our font, so we'll start at tile 384 (256+192)
|We're going to use the function DefineTiles to transfer
the bitmap data to Vram... the source will be defined by zeropage
addresses defined as z_H and z_L
The Bytecount will be defined by z_B and z_C
the destination memory address will be defined by z_DE.... each tile is 32 bytes, and we're going to write to tile 256+128 (384)... so we'll be writing to memory address $1800
Once our tiles are defined, we going to draw them to the screen with the FillAreaWithTiles command... this takes an X and Y pos in z_B and z_C
We also need a width and height - which we'll store in X and Y...
We'll load the first tile as 128 into A - though the function will add 256 automatically (for compatibility with systems other than the PCE)
|Our PrepareVram function will send the address in z_HL to the address select register at $0102 (ST1) and $0103 (ST2) - this will prepare Vram to Write (or read) data|
|We're going to
create a function called GetVDPScreenPos - this will convert an XY
co-ordinate in z_b and z_c into a memory address - effectively setting
the VRAM write location for the selected tile number
We do this by multiplying our Ypos by 32 (the width of each line in tiles) and adding the Xpos.
We have to split the Ypos calculation into two parts, as part of this needs to be passed in the High byte,
Finally, we have an extra option called ScrWid256 - this will center the screen to simulate a 256x192 screen (used for Grime 68000)
|Remember, We're using $0102 and $0103 to write data - you may see elsewhere $0002 an $0003 used for the same purpose, but this confuses VASM - as it mistakes them for the zero page... But whatever you use the result is the same, as the ports repeat every four bytes!|
function will use the PrepareVram function to get the memory ready to
We'll then stream the bytes to vram via Register 2.... we have to write in pairs, as the Vram works in Words...
We keep repeating the process until z_bc reaches zero
|Now we've defined our tiles, it's time to set the
Tilemap to show them, we do this with FillAreaWithTiles...
This will use the GetVDPScreenPos function to calculate a memory address, and then we'll write tile numbers to the vram - into the tilemap.... note, the high byte is 1 - as all tile numbers on the PcEngine have 256 added to them
We loop until we get to the end of the line, then we repeat - recalculating our VDPScreenPos until we've done all the lines.
By the end of this procedure, our character will be drawn to the screen!
|We've seen how to use tiles to get
graphics on the screen, we do the same for the font, we've just defined
the first 128 characters as our letters, and we set parts of the
tilemap as needed...
We can do the same to make a simple game, see Grime 6502... if you want to do better graphics, you want to use sprites, we'll look at those later!
|As with everything else on the PC engine Vram... each
tile definiton takes one memory address, which contains 2 bytes...
The top 4 bits pppp define a 16 color palette number from 0-15...
The remaining 12 bits nnnn nnnnnnnn define the tile number -
as stated, the first 64-256 probably can't be used because they overlap the tilemap... no's 2048-4095 CANNOT be used, as the memory these would use would be 64k-128k... and this memory is not installed in the PC Engine.
|If you use
AkuSprite Editor (included in the Sources.7z) you won't need to worry
about the data format of the tiles - as it's a little odd!
But if you do, then you'll need to know about the format, shown below!
|Byte 1||Byte 2|
|First 16 bytes||00111100
|Second 16 bytes||00333300
| Lesson P6 - Bitmap Functions on
the NES / Famicom
The NES and Famicom use a graphics system called the PPU - they are also Tile / Sprite based systems, with a grid of 8x8 tiles in the background.
As with the PCE, we'll use these to draw our Chibiko character bitmap to the screen as a set of tiles.
|The PPU is the NES
and Famicom's Graphics system, it's
controlled by 8 registers between $2000 and $2007... we use these to
check and set attributes of the system. and write to VRAM (which isn't
in the normal memory map!)
Some of these ports take two bytes - both should be written consecutively to the same port...
Strangely, when we want to write to VRAM, it's in Big Endian mode - so we have to send the High byte, then the Low byte... the opposite of the normal 6502!
B=Back Pattern Table
P=sprite Pattern table
I=Increment vram address
AA=Name Table address
I=0 means +1 , I=1 means +32
s= 0 - hides leftmost 8 pixels
b= 0 - hides leftmost 8 pixels
|$2002||PPUSTATUS||Read resets PPUSCROLL|
|$2004||OAMDATA||Sprite data (to write to addr, autoincs)|
|$2005||PPUSCROLL||XXXXXXXX YYYYYYYYY||Select X offset and Y Offset|
|$2006||PPUADDR||HHHHHHHH LLLLLLLL||Select Address to write to (Big Endian!)||Write resets PPUSCROLL|
|$2007||PPUDATA||BBBBBBBB||Byte to write to address in $2006|
|Specify a memory address by writing the byte pair to $2006... HIGH BYTE
FIRST... (Big Endian)
NOTE: Writing to VRAM outside of VBLANK will cause problems... also note, selecting an address resets PPUSCROLL
EG, lets point to $3F00... and write $11 to the first palette entry!
lda #$3F ;High byte
sta $2006 ;Send to vram select
lda #$00 ;Low byte
sta $2006 ;Send to vram select
lda #$11 ;New value
sta $2007 ;Send to write data
|First 8 bytes||00111100
|Second 8 bytes||00222200
need to worry about the tile format if you use AkuSprite Editor - the
open source sprite editor supplied with these tutorials will do the
work for you.
The NES 'Save Raw bitmap' option was used to convert the sample bitmap into a grid of tiles in the correct format for the NES!
|We need to turn on the screen before we can see anything, we
do this with port $2001... we also need to turn on our VBLANK
interrupts with the PPUCTRL
The NES graphics system has an annoying limitation!
Whenever we write data to the VDP it will mess up the display scroll position, so there may be times we need to turn the screen off - as a way of solving the problem, we do this by clearing the same two ports
|As writing to the PPU will mess up the scroll position, we'll define a function to reset it.
This sets the correct values to position the tilemap, so the byte at VRAM address $2000 is the top left of the tile map
|We can't write to VRAM outside of VBLANK (The time the screen
is not being drawn)... because of this we're going to need a function
to wait for VBLANK to restart.
We do this by checking a zero page entry 'Vblanked' - and waiting for it to change... our interrupt handler will change this value when the interrupt occurs
We'll see an interrupt handler later in the lesson!
|Every time we write to the screen,
we need first wait for VBLANK (when the screen isn't being drawn) and
also reset our scroll after we're done.
This is a real pain, but we'll learn a trick to work around it!
|We're going to show our Chibiko character to the screen!
The bitmap is 48x48 - and so as our tiles are 8x8, we'll be splitting it up into a grid of 6x6 tiles (36 tiles)
We'll use two commands to do this, one will send our bitmap data to the VDP defining the Tile patterns, the other will set the Tilemap to show those patterns to the screen
|The code we'll be using to get
the bitmap to the screen is the same as on the PC engine... the only
difference is the destination address of the data...
As the first 128 tile patterns are used by our font, we'll be using tiles 128+... as NES tiles are 2bit per pixel (4 color) each 8x8 tile is 16 bytes... so tile 128 is at memory address $0800... we load this into z_de zero page addresses
We load the source bitmap into z_hl and the size of the bitmap in z_bc
the DefineTiles function will copy the data into the VRAM
Once the tiles are defined, we set the XY pos to draw to in z_bc, and the WIDTH/HEIGHT in the X/Y registers... we also set the first tile in register A , then we call FillAreaWithTiles to draw the bitmap to screen
|We're going to define our Chibiko character...
Sending data to VRAM will mess up the screen, so we'll first turn off the screen,
We'll then use the PrepareVRAM command to select the destination address
Once that's done, all we need to do is send each of the bytes of our character to address $2007 (PPUDATA)
After all the data is sent, we turn the screen back on!
|The Prepare VRAM command is
also very simple, to select the memory address in zero page entries
z_de, we just write the high byte z_d to address $2006 (PPU ADDR), then
the low byte z_e to the same address..
This sets the address the data will be written!
|As we saw before, FillAreaWithTiles will set the visible tiles to a range of defined patterns.
The first tilenumber is in A, the width is in X, the height is in Y, and the start XY pos is in z_bc
We use GetVDPScreenPos to select the correct position in the tilemap, then we write our tile number into VRAM to show that tile in the selected position... VRAM auto-increments, so we can just write consecutive tile numbers until we get to the end of the line, once we 've completed a line, we jump back and re-run GetVDPScreenPos
Writing to VRAM messses up the scroll position - so we reset it once we're done... there is a way to avoid this, which we'll learn in a moment!
|Calculating the VRAM position is
not hard, as the tilemap is 32 wide, we multiply the Y pos by 32, add
the X pos and the base of the tilemap ($2000)...
We do the multiplication by bit-shifting.
Once we've calculated the address, we need to wait for VBLANK - as we can't write to VRAM at any other time
Then we send it the PPUADDR.... high byte then low byte!
above works for an example, but it's useless in games, as we keep
having to wait for VBLANK before each write, and reset our scroll
We need to get smarter! we're going to store up all those writes, and send them in VBLANK while the screen isn't being drawn...
The result will be no waiting around, and no visual corruption caused by the scroll changing!
|Here is our 'new and improved' GetVDPScreenPos command...
It's mostly the same, but the last part has changed
We're now using the function 'Get VDPBufferCT'... this will check the buffer isn't full, and return the position that we can write our new data into in the Y register...
The function writes the new destination address into the buffer VDPBuffer, leaving us to write the last one ourselves
|The GetVDPBufferCT function will
read the buffer position from the zero page entry VDP_CT... if the
buffer is full it will wait for VBLANK,
When the buffer is not full, it will return Y pointing to the next entry... but it will temporarily set the buffer size to Zero, so if an interrupt fires, it doesn't process an incomplete queue
|Our FillAreaWithTiles has also changed...
We now need to call GetVDPScreenpos for every byte, as we need one address header in our buffer per written byte...
Then we update our buffer... We then write our new byte, and save the new buffer count in zeropage VDP_CT
|We define the address of our interrupt handler at the top of the memory map, at $FFFA
We need to point to the address of our interrupt handler, that will handle the Vblank
|Here's our Interrupt handler that will run during VBLANK.
First we're starting the Sprite DMA with $4014... don't worry about this - it's for a later lesson!
Next we load in the waiting command count from VDP_CT...
For each waiting command, we write the HL address to $2006, and the new byte value for that address to $2007
We repeat until all the bytes are done
Once all the bytes are written, we zero the buffer count in VDP_CT,
As we've written to VRAM, we need to reset the scroll position as before.
|This buffer code works, but it's pretty basic and crappy!
A better one may support RLE - where lots of bytes are all filled with the same value,
This code also wastes a lot of buffer bytes - as each written byte has an address pair... if all the bytes are consecutive, this is very wasteful!
| Lesson P7 - Bitmap Functions on
the SNES / Super Famicom
The SNES uses 16 color 8x8 tiles... like the NES, we use memory mapped ports to control the PPU...
We need a bit of set up to get the screen working, but once it's done, things are pretty easy.
|w||$2100||INIDISP||Screen display||x000bbbb||x=screen disable (1=disable) bbbb=brightness (15=max)|
|w||$2101||OBSEL||OAM size (Sprite)||sssnnbbb||sss^size nn=name addr bb=base addr|
|w 2||$2102||OAMADDL/H||OAM address||aaaaaaaa r000000m||a=oam address r=priority m=addr MSB|
|wd||$2104||OAMDATA||OAM data||???????? ????????|
|w||$2105||BGMODE||Screen mode||abcdefff||abcd=tile sizes e=pri fff=mode def|
|w||$2107||BG1SC||BG1 Tilemap VRAM location||xxxxxxab||xxx=address ab SC size 00=32x32 01=64x32 10=32x64 11=64x64|
|w||$210B||BG12NBA||BG1 & BG2 VRAM location||aaaabbbb||aaa=base addr for BG2 bbb=base addr for BG1|
|wd||$210D||BG1HOFS||BG1 horizontal scroll||mmmmmaaa aaaaaaaa||aaa=horiz offset, mmm=Mode 7 option|
|wd||$210E||BG1VOFS||BG1 vertical scroll|
|w||$2115||VMAIN||Video port contro l||i000abcd||I 1=inc on $2118 or $2139 0=$2119 or $213A… abcd=move size|
|w 2||$2116-$2117||VMADDL/H||Video port address||LLLLLLLL HHHHHHHH||Memory address (in bytepairs）$0000-$7FFF|
|w 2||$2118-$2119||VMDATAL/H||Video port data||LLLLLLLL HHHHHHHH||Byte Data|
|w||$2121||CGADD||Colour # (or pallete) selection||xxxxxxxx||x=color (0-255)|
|wd||$2122||CGDATA||Colour data||-bbbbbgg gggrrrrr||Color Data BGR|
|w||$212C||TM||Main screen designation||---S4321||S=sprites 4-1=enable Bgx|
|$7FFF||Last byte of ram|
|V||H||L||P||P||P||T||T||T||T||T||T||T||T||T||T||V=vflip H=hflip L=layer (in front of sprites) P=palette T=tile number|
|We're going to initilize the screen, first we need to
set the position of our background tilemap... we're using the
settings shown above, with BG1 at $0000
we're also setting the tilemap size - we're using a 32x32 tilemap in these examples.
|We need to turn on the layer (we're using layer 1), we'll also set the screen brightness!|
|We're going to set up a basic palette, with blue as the background color, and the first 4 colors set...
To set a color, we write the palette inxex to $2121
Then we write 2 bytes of the color definition to $2122
|We need to do something quite important next... when we write
data to the ports $2118 and $2119... depending on this setting we'll
inc on the High byte, or Low byte being written... we're going to set it up to update on $2118 - the low byte
Now we're going to set up the screen mode, load the font, and finally set up the scroll position
We're going to set the scroll so that the top left of the screen shows the first tile in the tilemap
Finally our screen is set up!
|We can't write to the VRAM while the screen is being drawn, as it will cause problems... so we need to wait for VBLANK...
Vblank is where the screen has been drawn completely, but redraw has not restarted yet....
We can detect this by tesing bit 7 of $4212... we'll be using this command a lot!
|While we have to wait for VBLANK on the snes, unlike the NES we don't have to reset the scroll position...
Like the NES, we're going to learn a better way of doing things later in the lesson!
|The code is the same as the NES/PCE
We uise z_hl as our source data, z_bc as the size of the data, and z_de as the destination in VRAM.
Our tiles are 16 color 4bpp... so we'd expect our 8x8 tiles to take address entries... but they only take 16!
You see, on the SNES, each address holds a word - two bytes... so we're going to write our data to $1800... $1000 (the pattern base) + $800 (128x16)
We'll then use DefineTiles to transfer the data to VRAM
Once we've defined the tile patterns, we need to get them to the screen... we load the XY pos to draw to into z_bc and call DefineTiles... this will draw our bitmap onto the screen
|You can see our character drawn to the screen!|
can create SNES format tiles with Akusprite Editor... in fact AkuSprite
editor can make graphics for EVERY SYSTEM IN THE ENTIRE WORLD!!!
OK, that's a total lie... but it can do every system covered in these tutorials... and that's quite impressive isnt it?
|As we just saw the DefineTiles function transfers z_bc bytes of data from z_hl in ram to z_de in VRAM
To set up the destination address we use prepareVRAM, this will select the memory address we want to write to, by sending the HL address of the VRAM to write to $2116 and $2117
BUT... before we can write to these ports we need to be sure we're in VBLANK, so we call WaitVBlank, which will pause untill we are!
We can now transfer from z_hl to the PPU
We now write our byte pairs to $2119 and $2118... we've set up our PPU to auto-increment the destination address whenever we write to $2118... remember each address in VRAM takes 2 bytes (a word)
We loop until we've transferred all the data bytes in z_bc
|We're now going to set the tiles in the tilemap to show tile patterns we just defined in a grid!
We're going to use a command 'GetVDPScreenPos' to covert an XY position into a memory location in the tilemap
Once again, we need to wait for VBLANK, before we write our data... .once we're in Vblank, we can write our tile numbers... each takes two bytes, but we'll be writing the top byte as #0 (limiting us to 256 tiles)
We can just keep wriing tiles until we get to the end of the horizontal line, then we recalculate the memory position at the start of the next line.
We repeat until the area is filled.
for Vblank every time isn't as bad on the SNES as it was on the NES due
to the speed of the system, ... but it's not really good enough either
- there were some graphical glitches on Grime 6502 on the SNES... so
once again we're going to buffer the data, and send it in VBLANK....
But the SNES makes it easier for us, as we have a DMA which will do a big copy for us during VBLANK!
|w||$4201||WRIO||Programmable I/O port (out-port)|
|w 2||$4204||WRDIVL/H||Dividend C|
|w 2||$4207||HTIMEL/H||Video H IRQ beam pos/pointer||0000000x xxxxxxxx||x: Beam position.|
|w 2||$4209||VTIMEL/H||Video V IRQ beam pos/pointer||0000000y yyyyyyyy||y: Beam position.|
|w||$420C||HDMAEN||HDMA enable .|
|w||$420D||MEMSEL||Cycle speed||0000000x||0=2.68 1=3.58|
|r||$4210||RDNMI||NMI||x000vvvv||x=disable NMI v=version|
|rw||$4211||TIMEUP||Video IRQ||i0000000||i=irq enabled|
|rw||$4212||HVBJOY||Status||xy00000a||x=vblank state y=hblank state a=joypad ready|
|r||$4213||RDIO||Programmable I/O port (in-port)|
|r 2||$4214||RDDIVL/H||Quotient of divide result|
|r 2||$4216||RDMPYL/H||Multiplication or divide result|
|w 2||$43x2||A1TXL/H||Source address|
|w||$43x4||A1BX||Source bank address|
|w 2||$43x5||DASXL/H||DMA transfer size & HDMA address|
|w||$43xA||NTRLX||Number of lines for HDMA transfer||cxxxxxxx||C=continue (0=yes) x=lines to transfer|
|We're going to use a buffer
this time... this will be held in ram, and cover the entire 2048 bytes
of the tilemap from the ram into the VRAM.
Even better, we don't need to do this ourselves! We can use the SNES DMA (Direct Memory Access) to copy the data.
The DMA command is designed to work with the destination registers $2118 and $2119 for VRAM data, but we're going to need to specify the destination address in Vram ($0000) and configure the AutoInc to update on $2119 (we were updating on $2118)
We have to specify the source address as a 24-bit address, because although we're working in 6502 mode, the 65816 is a 24 bit CPU.
At the enbd we start the DMA with port $420B... note we turn off something called 'H-DMA' (Horizontal DMA)... we don't need it to copy with VBLANK, but it shares the same settings
The DMA will transfer the data... the CPU effectively halts during the transfer, so the job is done before our next command
we then turn the AutoInc back to update on $2118, and return from interrupts with RTI
|We need to add pointers to the footer of our ROM file - the
CustomNMIHandler is included in the NMI position...
Note there are two versions - one for the 65816 mode, and one for 6502 mode (though our tutorial uses 6502 mode)
|We need to change our GetVDPScreenPos... we now caclulate the memory position in the SnesScreenBuffer for the address... and store that address into z_hl|
|The FillAreaWithTiles function has been changed, it now writes the Tile number into the z_hl address.... this will then be copied into VRAM during the next VBLANK|
code is much easier thanks to the DMA... on the NES we had to buffer
just a few changes, and send the changes during the VBLANK...
Having enough RAM and speed to buffer the whole tilemap is a big help... we're still sending the Tile patterns in the same way, waiting for Vblank, but we'll probably only do that a the start of our game/level anyway.
| Lesson P8 - Bitmap Functions on
The VIC-20 can't do Bitmaps....
No bitmaps at all!
So why do we have a Bitmap Functions lesson?... well, we can use custom characters like tiles, and do the job that way!