================== IO Programming (2) ================== In this page, the color codes of the standard palette are used throughout. As a quick reminder, here is a subset of them (see ioref for the complete list): STD PALETTE --+-------- 0|BLACK 1|WHITE 2|RED 3|CYAN 4|PURPLE 5|GREEN 6|BLUE 7|YELLOW IO Requests =========== The IO Chip (MIDP1/MIDP2) is able to execute some sort of instructions called "requests". For example, this is a request: 17 0 and it means "set the background color to BLACK". This is another one: 17 0 0 255 and it means "set the background color to pure blue (i.e. RGB 0,0,255)". While this one: 17 0 0 is an invalid request. The first byte of a request (17 in the examples above) tells you what the request is all about ("set the background color", or SETBGCOL in the examples above). Unlike CPU instructions, requests are "sent" to the IO Chip. Also, unlike CPU instructions you cannot tell how long is a request by looking at its first byte. Suppose you want to send the request 17 5 to the IO Chip to set the background color to GREEN. First you send one byte after the other by writing into REQPUT (2:1): LDA #17 STA 2:1 LDA #5 STA 2:1 Then you signal the end of the request by writing any value you like into REQEND (2:2): STA 2:2 Now, suppose you want to send the request 17 20 100 255. Using 2:1 and 2:2, that would take 9 (i.e. 4*2 + 1) assembly instructions. There is another way. You can store a sequence of bytes like this (note the first two bytes stating how long the request is; in this case 4 bytes): 4 0 17 20 100 255 starting from, for example, 7:50 and then send the request writing the address (including the 2-bytes length) into the registers REQPTRHI (2:5) and REQPTRLO (2:4): LDA #7 STA 2:5 LDA #50 STA 2:4 The order is important! The sending starts when you write into 2:4. When sending multiple requests, you don't need to rewrite into 2:5 if it doesn't change. This way of sending a request by telling the IO chip where it starts in memory is called DMA (for Direct Memory Access). Enable ====== When you write into FRMDRAW (2:18), the CPU is blocked until the next frame is due and the following elements are drawn: 1) A rectangle covering the whole frame using the color set by the request SETBGCOL (default: WHITE) 2) The image configured by the request SETBGIMG (default: no image) 3) The matrix of characters set by CONVIDEO, CONCCHR, CONCFG, etc..., a.k.a. the Console 4) Sprites and Tiled Layers configured by the requests LSPRITE/LTILED, a.k.a. the Layers You can configure which of the steps above are actually carried out by adding together a combination of the following: ENABLE -+------- 1|BGCOL 2|BGIMG 4|CONSOLE 8|LAYERS and writing the result into ENABLE (2:16). For example, by default, the value stored in 2:16 is 5. 5 is 1+4. So, by default, the background and the console are drawn. As a last example, this program: LDA #17 STA 2:1 LDA #5 STA 2:1 STA 2:2 results in a green screen with a white rectangle in the middle (i.e. the console), while this program: LDA #1 STA 2:16 LDA #17 STA 2:1 LDA #5 STA 2:1 STA 2:2 results in just a green screen, because the console has been disabled. Creating images =============== The main request to create an image (IPNGGEN) is fairly complex and is usually generated by the Paint module. However, studying how that request is composed can help you to demystify the whole concept of images. Here is a 20 bytes request to create a small image. (20 0) 25 1 8 0 8 0 1 3 3 1 2 7 126 255 219 255 195 231 255 126 I prefixed the request with a 2-bytes length (20 0) because it is common to send images to the IO chip using DMA. First there is IPNGGEN (25), followed by the ImageId (1). The ImageId is discussed later. As long as you are consistent within your program, 1 is usually fine. Then there are 4 bytes for the width (8 0) and the height (8 0) of the image, followed by the Depth (1) of the image. For Depth you can choose one of the following: D|Col -+--- 1| 2 2| 4 4| 16 8|256 So, a Depth of 1 means 2 colors. The next byte (ColorType) is usually 3, meaning that a color palette is included in the image. The next byte (Flags) is a combination of the following: FLAGS --+---------- 1|IDX0TRANSP 2|PALREF 4|ZOOM0 8|ZOOM1 16|ZOOM2 On the example above, Flags is 3, meaning that the first color of the image is unused / transparent (IDX0TRANSP) and colors are described by the color codes of the current palette (PALREF). ZOOM0, ZOOM1 and ZOOM2 are discussed later. Now the description of how the image looks like begins. First there is the palette of the image: 1 2 7 The first byte (1) is the number of entries minus 1. So, the first 1 means that the palette has 2 entries (same as the number of colors; but it could be lower). The next 2 bytes are the color codes. So, the first entry is RED and the second entry is YELLOW. However, since IDX0TRANSP above, RED pixels appear transparent. If Flags did no contain PALREF, each entry would take 3 bytes instead of 1, for the red, green and blue components of the color. Finally, there is group of bytes (Data) describing each pixel of the image. 126 255 219 255 195 231 255 126 Here is where these 8 bytes come from. If you draw a picture using X for YELLOW and . for RED: .XXXXXX. XXXXXXXX XX.XX.XX XXXXXXXX XX....XX XXX..XXX XXXXXXXX .XXXXXX. And then turn them into 0s and 1s: 01111110 11111111 11011011 11111111 11000011 11100111 11111111 01111110 You can use the following table: LO| % |$| HI --+----+-+--- 0|0000|0| 0 1|0001|1| 16 2|0010|2| 32 3|0011|3| 48 4|0100|4| 64 5|0101|5| 80 6|0110|6| 96 7|0111|7|112 8|1000|8|128 9|1001|9|144 10|1010|A|160 11|1011|B|176 12|1100|C|192 13|1101|D|208 14|1110|E|224 15|1111|F|240 Here is how it works: 1) Take the first row of the image (01111110) 2) Split it in two halves (also called "nibbles"): 0111 and 1110. 3) Find the first half (0111) in the table, and note the number on the right (112). 4) Find the second half (1110) in the table, and note the number on the left (14). 5) Add the two numbers together: 112 + 14 = 126. If you check, you will see that 126 is the first of the 8 Data bytes. Use the second row for the second byte, and so on. Showing images ============== Once you create an image, to show it you can send a SETBGIMG (19) request. Remember to use ENABLE (2:16) to disable the console and enable the background image. First create a program of 2 pages (1 page of code and 1 page of data). At 4:0, create a IPNGGEN request (see above for the explanation): 20 0 25 1 8 0 8 0 1 3 3 1 2 7 126 255 219 255 195 231 255 126 At 4:22, create a SETBGIMG request: 2 0 19 1 The byte following 19 is the ImageId. As stated above, use 1 and you will be fine. Now for the code (starting from 3:0). First, use ENABLE to enable the background image and to disable the console: LDA #3 STA 2:16 All the requests are on page 4, so setup the DMA to use page 4: LDA #4 STA 2:5 Now create the image (send the IPNGGEN request): LDA #0 STA 2:4 And show it (send the SETBGIMG request): LDA #22 STA 2:4 If you try the program, you should see a yellow image centered on the screen. Keep this program; it will be modified below to explore other aspects of the IO chip. References and slots ==================== Images (and layers, to be discussed later) are "objects" managed by the IO chip. You can keep references to images using slots. By default, there are 4 (image) slots (from 0 to 3) available. When you create an image, ImageId is the slot you use. The slot will hold a reference to the image. If the slot already referenced another image, that reference is removed. The example above used ImageId 1 and looked liked this: IPNGGEN 1 (image A) SETBGIMG 1 After these requests, the IO chip looks like this: ID Slots Images Users +---+ 0 | . | +---+ +-+ 1 | -------> |A| <------ BGIMG +---+ +-+ 2 | . | +---+ 3 | . | +---+ The request IDESTROY (20) removes a reference from a slot to an image. It is followed by the ImageId of the slot. Note that in the example above you could send a IDESTROY with ImageId 1 and the image would not be destroyed (BGIMG "keeps it alive"). A better name for IDESTROY might have been something like IUNLINK. Here is a more complex example. After the following requests: IPNGGEN 3 (image A) IDESTROY 3 IPNGGEN 0 (image B) SETBGIMG 0 IPNGGEN 0 (image C) The IO chip looks like this: ID Slots Images Users +---+ +-+ 0 | -----> |C| +---+ +-+ 1 | . | +-+ +---+ |B| <-- BGIMG 2 | . | +-+ +-+ +---+ |A| 3 | . | +-+ +---+ Image A is not used by anyone and the IO chip might destroy it if it needs more memory (a process called "garbage collection"). Image B is used by the IO chip (even if you have disabled the drawing of the background image with ENABLE). Image C is kept alive by the programmer, who is holding a reference to it in slot 0. You can increase (or decrease) the number of image slots by sending a IDIM (21) request, but this is usually not necessary. Layers (Sprites) ================ Layers are objects that you can easily move around the display. The name comes from the fact that when they overlap is as if they were stacked on top of each other. You manage layers using slots. It is just like images, but it is a different set of slots. By default, there are 16 (layer) slots (from 0 to 15) available. If you need to increase the number of layers you can use the request LDIM (35). It is followed by the last layer id (i.e. 31 if you need 32 layers). There are two kind of layers: sprites and tiled layers. Sprites are described first, as they are a bit easier to use. To create a sprite, you can use the LSPRITE (37) request. It is followed by the LayerId and by the ImageId. If you only use one sprite, you should use slot 0 for LayerId. How to handle multiple sprites is discussed later. Starting from the program from the "Creating images" section above, you can replace the SETBGCOL request with a LSPRITE request. So, starting from 4:22, you will have: 3 0 37 0 1 The value written into ENABLE should also be changed. You want to enable the drawing of the layers instead of the background image. So, starting from 3:0, you will have: LDA #9 STA 2:16 If you try the program now, nothing happens. This is because layers are created hidden. To show them, you have to write 128 into LCTL (2:81). So, at the end of the program (it should be at 3:20), add: LDA #128 STA 2:81 If you try the program now, you should see the sprite. Unlike the background image, the sprite is positioned at the top left corner of the display. But unlike background images, you can move sprites! Use LX (2:82) and LY (2:83). You can of course do so in a loop. For example, at the end of the program (it should be at 3:25), add: LDA #50 STA 2:83 INC 2:82 STA 2:18 JMP 3:30 As you can see, after the position of the sprite has been updated, the program writes to FRMDRAW (2:18). Multiple Sprites ================ To create more than one sprite, just send more LSPRITE requests. Make sure to give each sprite a different LayerId. You can use the same image several times or give each sprite a separate image. You can only control one layer (remember that sprites are just a type of layer) at a time. To tell the IO chip which one you want to control, write its LayerId into LID (2:80). The layer registers (i.e.: LCTL, LX, LY, etc...) will then refer to that particular layer. Initially, LID is set to 0; this is the reason why you don't need to worry about it if you only have one layer and its LayerId is 0. Here is how to modify the previous example to handle multiple sprites. Leave the data page as it is. You should have a IPNGGEN request at 4:0 and a LSPRITE request at 4:22. The beginning of the code is left unchanged: LDA #9 STA 2:16 LDA #4 STA 2:5 Then (starting at 3:10), the following loop creates 3 sprites: LDX #0 LDA #0 STA 2:4 STX 4:25 LDA #22 STA 2:4 STX 2:80 LDA #128 STA 2:81 DEC 4:13 INX CPX #3 BNE 3:12 Note how the requests are changed before they are sent to the IO chip: at each iteration, the color code of the image to be created (4:13) is decremented, and the LayerId (4:25) of the sprite to be created is incremented. Also note that LID (2:80) is updated with the LayerId of the newly created sprite, before enabling it by writing 128 into LCTL (2:81). After the code above, the IO chip looks like this: IMAGES LAYERS ID Slots Images Slots ID +---+ +-+ +---+ 0 | . | |Y| <------ | 0 +---+ +-+ +-+ +---+ 1 | ----+ |B| <------------ | 1 +---+ | +-+ +-+ +---+ 2 | . | +------> |G| <-------| 2 +---+ +-+ +---+ 3 | . | . +---+ . +---+ | . | 15 +---+ If you try the program now, you will see only one sprite. The other two sprites are really there; they are simply below the one you see. Add this loop at the end of the program (it should be at 3:41), to move them: LDA #0 STA 2:80 INC 2:82 INC 2:80 INC 2:83 INC 2:80 INC 2:82 INC 2:83 STA 2:18 JMP 3:41 Note how at each frame LID (2:80) is first reset to select sprite 0 and then it is incremented twice to select the other two sprites. For each sprite, LX (2:82) and LY (2:83) are changed differently. If you run the program now, the three sprites should move in a loop. More on images ============== When converting an image to bytes, turning X/.s into 1/0s was a trivial, but important step. X/.s are pixels. 1/0s are bits. The distinction will become relevant shortly, when multicolor images are discussed, but first, let's see how images of sizes other than 8x8 are converted. If the image is shorter or taller, it is not really a problem: a 8x2 image would just have 2 rows and a 8x100 image would just have 100 rows. If the image is narrower, you fill each row until you reach 8 bits. For example, the following 3x3 image: 010 111 010 should be treated like this: 01000000 11100000 01000000 Note that the width to use in the IPNGGEN request is still 3. If the image is wider, you need more bytes for each row; just group the bits in groups of 8 when you convert them. You still need to fill each row until you reach a multiple of 8 bits. For example, the following 10x3 image: 1111111111 1000000001 1111111111 should be treated like this: 11111111 11000000 10000000 01000000 11111111 11000000 Every byte of each row is sent starting from the first on the left, to the last on the right. To clarify, the Data of last image is 6 bytes long, and the order of the bytes is the following: 1 2 3 4 5 6 If the image has more than 2 colors, Depth means how many bits each pixel takes. Here is an example with Depth 2: #| %|Color -+--+------ 0|00|WHITE 1|01|RED 2|10|YELLOW 3|11|GREEN The following image: RR..GG ..YY.. can be turned into the following bits: 01 01 00 00 11 11 00 00 10 10 00 00 The bits can then be regrouped and each row can be extended to reach the first multiple of 8: 01010000 11110000 00001010 00000000 Here is the resulting request: (18 0) 25 1 6 0 2 0 2 3 3 3 1 2 7 5 80 240 10 0 If Depth is 4, the order of the bit patterns matching the image palette can be found on the table you used to convert bits to bytes. When you start using lots of images, available data pages can run out quickly. This is especially a problem with phones with a high resolution display. The IPNGGEN request for a 16 colors, 16x16 image takes up more than half a page... and 16x16 might be quite small on a tiny 320x240 display! You can add one of the following values (ZOOM0/ZOOM1/ZOOM2) to Flags: ZOOM --+-- 0|x1 4|x2 8|x3 12|x4 16|x5 20|x6 24|x7 28|x8 If, for example, you create a 8x8 image with a zoom factor of x3 (+8), the actual image is going to be a 24x24 image. Note that, while this helps you to keep your program small, inside the IO chip the image still consumes as much memory as a regular 24x24 image. On the other hand, phones with high resolution displays usually have lots of memory, so this might be an acceptable trade off. For the ZOOM flags to work, the width of the original image must be a multiple of 8. Another use of the ZOOM flags is to create a background tileset. If you create a 8x1 RGB image with a zoom factor of x8 (+28), the resulting image will be a 64x8 images that can be used for a 8x8 LTILED (discussed later). Here is an example request: (42 0) 25 1 8 0 1 0 8 3 28 7 0 0 0 ; BLACK 255 255 255 ; WHITE 255 0 0 ; RED 0 255 0 ; GREEN 0 0 255 ; BLUE 255 255 0 ; YELLOW 0 255 255 ; CYAN 255 0 255 ; MAGENTA 0 1 2 3 4 5 6 7 This is one of the few cases where a Depth of 8 is useful: you don't need to use binary numbers for Data, as every pixel is exactly 1 byte (i.e. 8 bits), and the Data section is still small as the original image is small (i.e. only 8x1 pixels).