NINTENDO ______ ____ / ____/___ _____ ___ ___ / __ )____ __ __ / / __/ __ `/ __ `__ \/ _ \/ __ / __ \/ / / / / /_/ / /_/ / / / / / / __/ /_/ / /_/ / /_/ / \____/\__,_/_/ /_/ /_/\___/_____/\____/\__, / /____/ Programming Tutorial By David Pello (ladecadence.net) _n_________________ |_|_______________|_| | ,-------------. | | | .---------. | | | | | | | | | | | | | | | | | | | | | | | | | | | | `---------' | | | `---------------' | | _ GAME BOY | | _| |_ ,-. | ||_ O _| ,-. "._,"| | |_| "._," A | | _ _ B | | // // | | // // \\\\\\ | | ` ` \\\\\\ , |________...______,"
To begin this tutorial development for gameboy, let's review a little the technical characteristics of our wonderful console:
Well, in the heart of the GameBoy, have a CPU itself manufactured by Sharp for Nintendo, with the technical name of LR35902, we have a microprocessor somewhere between 8080 and Z80, because although it has the extra sets of registers Z80 or the index, if incorporates most of its extended instruction set, such as bit manipulation. It also incorporates additional circuitry for controlling the LCD, the joypad, serial and audio generation.
The CPU, let's call GBZ80 is a 8bit cpu with a bus of 16 bit addresses. That is, the data internally and in external memory are organized in bytes, and can address 2 ^ 16 = 64KB of external memory.
The GBZ80, has several internal registers, which can store data while operating with them and move them to or from the external memory to the CPU. These 8-bit registers are: a, b, c, d, e, h l.
Then there is the special register 'f', that saves the state of the processor flags. The processor to perform certain operations, can enable or disable some of these flags, which will be very useful when it comes to programming. For example, a bit in this register is the Zero flag, which indicates if the result of the previous operation, has been zero or not. Not all operations modify the flags, but many of them if they do. To get to know all the operations you can perform GBZ80, you can take a look at the relevant section of the Pan Docs: http://gbdev.gg8.se/wiki/articles/CPU_Instruction_Set
F registry flags are as follows:Los flags del registro f, son los siguientes:
Bit Nombre (1) (0) Explicación 7 zf Z NZ Flag Zero 6 n - - Flag addition / subtraction (BCD) 5 h - - Flag half carry (BCD) 4 cy C NC carry flag 3-0 - - - Not used (always zero)
The most commonly used flags are:
This bit is set to 1 if the operation result was zero (0). Widely used in conditional jumps.
It is set to 1 when the result of a sum is greater than $ FF (8bit) or $ FFFF (16bit), or when the result of comparison or subtraction is less than zero. In addition it puts one on one instruction when rotation or displacement, the operation takes to be a 1. Used in conditional jumps or instructions as ADC, SBC, RL, RLA, etc.
Some of these registers can be combined to form 16-bit registers, very useful for managing memory addresses or if we need larger numbers. The 16-bit registers are: AF, BC, DE, and HL. We also have two other special registers, the PC, and program counter, 16 bits, which stores the memory address of the next instruction to be executed, and the sp, or stack pointer, also 16 bits, which stores the pointer the battery.
The main memory of the gameboy, mapped 16-bit space, allows us to directly address 64K (2 ^ 16 = 65536). In this address space, we have to address all memory blocks to which you need access gameboy, ie RAM, ROM cartridge, internal RAM cartridge games record games, video memory, etc. For designers it GameBoy memory mapped in different blocks necessary, as internal RAM and video memory, leaving two blocks to access 16K ROM games, and 8K block access the RAM of the games (savegames). As many games began to require more than 32K of ROM or RAM 8K saved, he began to use a technique called "Banking", in which the ROM of the game is divided into several blocks that may wean (graphics or sounds of each screen for example), to be mapping in the block memory access as needed. In the Game Boy, this is designed as follows; We have a fixed block of 16K (where the main logic programmed the game), and then, by certain instructions (depending on the chip mapping that we use in our cartridge), we can go exchanging 16K banks in the other block available. It sounds complicated, but we'll talk about later banking. All this is reflected in the following memory map with all the available blocks in the address space of the GameBoy.
General map memory banks writing records ------------------------- ---------------------------------- Activation record Interruptions ---------------------------------- $FFFF High Internal RAM ---------------------------------- $FF80 Not usable ---------------------------------- $FF4C Ports E / S ---------------------------------- $FF00 Not usable ---------------------------------- $FEA0 Sprite attributes (OAM) ---------------------------------- $FE00 Mirror of internal RAM 8KB ---------------------------------- $E000 8kB of internal RAM ---------------------------------- $C000 ------------------------------ Interchangeable 8KB RAM bank / Selector MBC1 ROM / RAM / Selector MBC1 ROM/RAM ---------------------------------- $A000 / ----------------------------- 8kB Video RAM / / Ram bank Selector ---------------------------------- $8000 --/ / ---------------------------- 16kB interchangeable ROM bank $6000 ----/ / Selector bank ROM ---------------------------------- $4000 ------/ --------------------------- 16KB ROM Bank # 0 $2000 --------/ RAM Bank Activation ---------------------------------- $0000 ------------------------------------
* NOTE: b = bit, B = byte
* NOTE: Using RGBDS convention, the assembler will use in the tutorial, the numbers in hexadecimal notation, write with a "$" in front, in binary with a "%" and without decimal code.
GameBoy ROMs have to keep some structure for the GameBoy accept them, especially in the subject header. Below I detail the parts of a ROM and its function:
OUTLINE OF AN IMAGE GameBoy ROM ------------------------------------ $0 - $100: Interrupt vectors. Although it is also possible to use these directions to get your code $100 - $103: Entry point to the implementation of the program, normally usually put a nop, followed by a jump to our own starting point. $104 - $14E: Header cartridge. Contains nintendo logo (104h-133h) which it is compared with that in the right boot rom the console, if not coiciden, execution stops. Then we have the following data: $134 - Cartridge Name - 15bytes $143 - Gameboy Color Support $80 = GBColor, $00 or other = B/N $144 - license code, 2 bytes (not important) $146 - supergameboy Support (GBS) 00 = GameBoy, 03 = Super GameBoy $147 - cartridge type (only ROM, MBC1, MBC1 + RAM etc ..) 0 - SOLO ROM 12 - ROM+MBC3+RAM 1 - ROM+MBC1 13 - ROM+MBC3+RAM+BATT 2 - ROM+MBC1+RAM 19 - ROM+MBC5 3 - ROM+MBC1+RAM+BATT 1A - ROM+MBC5+RAM 5 - ROM+MBC 1B - ROM+MBC5+RAM+BATT 6 - ROM+MBC2+BATT 1C - ROM+MBC5+RUMBLE 8 - ROM+RAM 1D - ROM+MBC5+RUMBLE+SRAM 9 - ROM+RAM+BATT 1E - ROM+MBC5+RUMBLE+SRAM+BATT B - ROM+MMM01 1F - GBCamara C - ROM+MMM01+SRAM FD - Bandai TAMA5 D - ROM+MMM01+SRAM+BATT FE - Hudson HuC-3 F - ROM+MBC3+TIMER+BATT FF - Hudson HuC-1 10 - ROM+MBC3+TIMER+RAM+BATT 11 - ROM+MBC3 $148 - Size ROM 0 - 256Kbit = 32KByte = 2 banks 1 - 512Kbit = 64KByte = 4 banks 2 - 1Mbit = 128KByte = 8 banks 3 - 2Mbit = 256KByte = 16 banks 4 - 4Mbit = 512KByte = 32 banks 5 - 8Mbit = 1MByte = 64 banks 6 - 16Mbit = 2MByte = 128 banks $52 - 9Mbit = 1.1MByte = 72 banks $53 - 10Mbit = 1.2MByte = 80 banks $54 - 12Mbit = 1.5MByte = 96 banks $149 - Size RAM 0 - None 1 - 16kBit = 2kB = 1 banks 2 - 64kBit = 8kB = 1 banks 3 - 256kBit = 32kB = 4 banks 4 - 1MBit =128kB =16 banks $ 14A - Area Code 0 - Japanese 1 - No Japanese $ 14B - old license code, 2 bytes $ 33 - find the code at $ 0144 / $ 0145 (SGB functions do not work if not $ 33) $ 14C - ROM version (normally $ 00) $ 14D - Test complement (important) $ 14E - Checksum (not important) $ 14F - $ 3FFF: Our code. This is the bank 0 16K, and is fixed. $ 4000 - $ 7FFF: Second 16K bank. At the start of the gameboy this is It corresponds to bank 1 (up to 32K of ROM), but could be exchanged for 16K top banks to total ROM we have available using a mapper as MBC1.
The video system of the GameBoy, is not a system of direct access pixel by pixel, as in a PC, but a system of blocks or "tiles". It consists of a buffer of 256 × 256 pixels (32 × 32 tiles) from which you can display 160 × 144 at all times (actual screen size, 20 × 18 tiles). We have a couple of scrollx and scrolly, records that allow us to move the buffer to the viewable area. Furthermore the buffer is circular, when done scroll beyond one end of the buffer, we begin to display data on the opposite side.
^ | v +------------------------------------+ |(scrollx, scrolly) | | +----------------+ | | | | | | | | | | | | | | | 20x18 | | | | Visible area | | | | | | | | | | <-> | | | | <-> | +----------------+ | | | | 32x32 | | Background map | | | | | | | | | | | +------------------------------------+ ^ | v
The registers that control the video system are very important because they allow us to control what we show on screen, and how. The most important to start with are:
This control register allows us to adjust the screen GameBoy, which we handle for any operation that involves showing something in it
Each bit of this register has a special siginificado, which I'll detail:Cada bit de este registro tiene un siginificado especial, que pasaré a detallar:
Bit 7 - Control del Display (0=Off, 1=On) Bit 7 - Control Display (0 = Off, 1 = On) Bit 6 - Selection Window Tile Map (0 = 9800-9BFF, 1 = 9C00-9FFF) Bit 5 - Window Control (0 = Off, 1 = On) Bit 4 - Select Data Tile background and window (0 = 8800-97FF, 1 = 8000-8FFF) Bit 3 - Selection Map Tile background (0 = 9800-9BFF, 1 = 9C00-9FFF) Bit 2 - Size of the OBJ (Sprites) (0 = 8x8, 1 = 8x16) Bit 1 - Control of the OBJ (Sprites) (0 = Off, 1 = On) Bit 0 - Control the background (0 = Off, 1 = On)
We will detail the functions of a bit, because this is very important:
Enables or disables the LCD. The display must be activated before we can show something in it, and sometimes we will want to disable it if we need to write a lot of data on screen as an image or full prensentación end, that time to put all the graphics memory before the display begins to draw graphs and see rare and half things. WARNING - The display can only be switched on and off when we are in the vertical interval period (V-Blank). Activate or deactivate outside this period may damage the hardware. Very careful about this, because in the emulators nothing happens, but you can spoil your gameboy if you do it in the real hardware. So to enable or disable the LCD, first waiting for the vertical interval (see how to do this).
Bit 0 - Control the background If this bit is zero, the background is not drawn, it goes blank (with which to draw the background we need to put this bit to one, of course). We have seen this in many games to flicker effects and stuff.
The remaining bits behave similarly and think are explained in the table.This record is also very important because it allows us to know who is doing the LCD at all times. We consult for numerous operations, so attentive. It also allows us to activate the LCD breaks, so as you see, we have a lot of functionality in this record.
An explanatory table and then we detail:
Bit 5 - Interrupt Mode 2 OAM (1 = On) (Read / Write) Bit 4 - Interrupt Mode 1 V-Blank (1 = On) (Read / Write) Bit 3 - Interrupt Mode 0 H-Blank (1 = On) (Read / Write) Bit 2 - Match Flag (0: LYC <> LY, 1: LYC = LY) (Read Only) Bit 1-0 - Flag Mode (Mode 0-3, see below) (Read Only) 0: We are in H-Blank 1: We are in V-Blank 2: Looking OAM-RAM 3: Transferring data to LCD
Bytes 3 to 5, let us turn interruptions LCD, very useful for certain processes requiring redrawn fast screen since writing our drawing code in them, we are confident that the drawing will occur properly (especially in Interval periods).
Byte 2, serves to compare two special registers, the LY ($ FF44), which is the Y coordinate where the LCD is currently drawing, and the LCY register ($ FF45), we can define us.
Bytes 1 and 0, indicate how the LCD is either in the two periods apart, or by accessing the RAM or writing on the LCD. As we said before, this is very useful to know whether we are in the period of VBlank for example; only would have to see if the bits 1-0 of this register, we have "01", this assembly of the gameboy is as simple as the following:
ld a, [$FF41] and 1 cp 1
This is loaded in the registry, the value we have in the record status LCD, and do NDA with 1. Any value that has the record, to give and to 1 if positions had in the last 01, the result will be 01, so compared with one (cf. 1). If now the result of the comparison is 0 (same), we are in the period of VBlank.
Although there is a faster way to know whether we are in V-Blank, and is using the registration LY I said before. As I said, the registration LY indicates that horizontal line is "drawing" the LCD. From the line 144, the LCD screen is off and therefore are in the vertical interval period. So you are doing this ...
ld a, [$FF44] cp 145
Carry in to the value contained in the registration LY, and compared with 145, if they are equal, just enter VBlank ... as we see, less instruction and assembly, this is very important. Thus, for example, that you should go see a little assembler gameboy, if we wait until we are in VBlank (to enable or disable the LCD for example, as we saw above), we could do something like:
.espera_vblank: ld a, [$FF44] cp 145 jr nz, .espera_vblank
Same as before, but after comparison, if the result is not zero (the LCD is not on the line 145), we jump to the initial label with which we return to do the checking ... this loop would run until the LCD reaches the 145 line, which will continue with the instructions below. Quiet with assembler, we will explain later.
Control registers scroll the display, as I explained above, allow us to move the visible window on the background map writing on them. To position the visible area in the coordinate (0,0) of the fund, simply write zero in both records.
Control the x, y position of the window. The window is an alternative fund, which can be drawn above the normal background to effects like zelda status screen. This window has no scroll, but may be positioned in any position by sliding using these registers. If placed in WY = 0, WX = 7, the window cover the entire visible background. The sprites are also above the window.
Then we have an area in the VRAM, known as "Background Tile Map" (map tiles background) 32 × 32 = 1024 bytes. Each of these bytes contains a reference to the number tile showing the "Tile Data Table" (data table tiles). Actually we have two background maps, one at $ 9800-9BFF and the other at $ 9C00-9FFF and can select either through the LCDC register ($ FF40). We also have a "window" that floats above the bottom, controlled by WNDPOSX and WNDPOSY records. We are also two Data Tables Tile, one that ranges from $ 8000 to 8FFF, unsigned (0-255) and can be used for background, sprites and window, and another going from $ 8800 to $ 97FF, with sign (-128 , 127) and can only be used for backgrounds. The drawings of the tiles 8 × 8 pixels are stored in the Tile Data Tables using 16 bits per line of the sprite, that is, two bits per pixel to store color data; since in the GameBoy have 4 possible colors: white, light gray, dark gray and black, in binary: 00, 01, 10 and 11
They are stored in memory as follows:
Of the two bytes of each line (this is complicated a bit), the first byte, receives the least significant bits of the color of each pixel, and the second the most significant byte being bit 7 more pixel to the left, and 0 pixel on the far right; that is, if the first pixel is dark gray (10) bit 7 of the first byte will be 1 and bit 7 of the second byte will be zero.
With a sprite what to draw an "A" multicolored have something like this:
Tile: Data: .33333.. %01111100 %01111100 -- $7C $7C (Hex) 22...22. %11000110 %00000000 -- $C6 $00 11...11. %00000000 %11000110 -- $00 $C6 2222222. <-- digits %11111110 %00000000 -- $FE $00 33...33. represent %11000110 %11000110 -- $C6 $C6 22...22. colors %11000110 %00000000 -- $C6 $00 11...11. %00000000 %11000110 -- $00 $C6 ........ %00000000 %00000000 -- $00 $00
So if we write 16 bytes $ 7C, $ 7C, $ C6, $ 00, $ 00, $ C6, $ FE, $ 00, $ C6, $ C6, $ C6, $ 00, $ 00, $ C6, $ 00, $ 00 from memory location $ 8000, the tile number 0, will be our "A" multicolored.
So now we have the map tiles background, as we said stored in a table of 32 × 32 (1024 bytes) numbers on the tiles to show our virtual screen of 32 × 32 tiles (32 columns x 32 lines) 20 × 18 visible). So, if now in the $ 9800 memory position, we write $ 00, we are saying that in the position tiles wallpaper (0,0), draw the tile 0, activating the LCD, and activate the background, now in the upper left corner of the screen, we would have our "A" multicolored.
IMPORTANT:
Keep in mind that you should not write to the video memory until we are in a period of range (either horizontal or vertical) or disabling the LCD. This is because strange things could happen if we access video memory to write to her, while the LCD is trying to read it ... we'll have to draw flashes and other unwanted effects. The same is true for sprites.
In addition to funds, of course we have the sprites. Sprites are graphics objects moving above the bottom, have a transparent color, and properties that can apply them as horizontal and vertical reflection. Normally the sprites are used to draw our characters, ships, enemies, and any graphic object that we want to handle regardless of background.
The video controller of the GameBoy, can draw sprites in modes 40 8 × 8 or 8 × 16 px px, though a hardware limitation, 10 sprites can be drawn simultaneously in a horizontal line (just 10 sprites can share the same coordinate Y). The graphics of the sprites are stored in memory in the same format as useful background in the table known as Sprite Pattern Table (Table patterns sprites), between $ 8000-8FFF memory locations, which can define up to 256 sprites (4096 bytes / 16 bytes per sprite). As we saw earlier, the $ 8,000 address is also used by the data table tiles background, so if the graphics for tiles and sprites are shared, we must take this into account when we design our graphics.
The attributes of the sprites, the sprites table that defines that we will use and how we use, reside in the Sprite Atributte Table, between memory locations $ FE00-FE9F. Each of these entries consists of 4 bytes, which have 160/4 = 40 entries, as stated above.
Each entry is made up as follows:
Specifies the Y position of the sprite least 8. How ?, the first visible pixel on the screen is vertically 8, this is so that we can put a little sprite or completely off the screen, drawing it between the 0-7 positions . Likewise we can get him out of the screen by drawing it into higher positions down to 140.
Specifies the X position of the sprite least 8. The same applies to the Y position unless we have 160 pixels visible on X coordinate
Specifies the sprite number to use for this entry sprites defined in Table $ 8000-8FFF.
Specifies the special attributes that can be applied to a sprite. Each bit of this byte is a flag that applies different sprite modifications according to the following table:
Bit7 Priority (0 = Sprite above the bottom, 1 = Object below background colors 1-3) (Valid for the background and the window. The color background 0 is always behind the sprite (transparent)) Reflection Bit6 Y (0 = Normal, 1 = Vertical mirroring) Bit5 Reflection X (0 = Normal, 1 = Horizontal Mirrored) Bit4 Number of pallet ** Color not only in GB ** (0 = OBP0, 1 = OBP1) Bit3 VRAM Bank ** ** Only GB Color (0 = Bank 0, 1 = Bank 1) Bit2-0 palette number ** ** Only GB Color (OBP0-7)
As we see, several bits are only applicable to GB Color, so for now we ignore it and focus on the priority, and mirrored palette.
To program in assembler for GameBoy, the first thing we need, what is it? As an assembler, the program that converts our written in assembly code to machine code gameboy, as a ROM that can run our favorite emulator or loaded directly into our Gameboy with flashcart.
Personally, I use the RGBDS, a development system specific assembly for GameBoy, so everything is very easy with him. The can download it from here:
Windows: There are builds for windows in:http://anthony.bentley.name/rgbds/
The descargais and you copy the .exe c: \ windows or the like within the PATH
Gnu/Linux: For a while, the RGBDS for Unix is hosted on GitHub: https://github.com/bentley/rgbds
In gnu/linux you have to compile it, but come on, it is simply run as root, a "make install" after downloading the git directory.
RGBDS you have documentation in: http://anthony.bentley.name/rgbds/manual/rgbds/
Well, RGBDS comes with four tools, rgbasm, the assembler itself, rgblink, the linker will create our roms, the rgblib to create libraries, and rgbfix to modify the head of the ROM, so that it meets the necessary requirements ROM to run. As we saw in the introduction, the head of a GameBoy ROM, takes several checksums and others, because this tool adjusted so that the rom is correct.
Also need some files that will make us easier to assemble the item and generate our roms, I will leave them below:
Windows: assemble.bat
Gnu/Linux: assemble.sh
NOTE - The source code, you must take the extension .agb (assembler gameboy) to run these scripts.
And finally, one more important help, a file that you can include in your source code that defines names for all records and memory addresses of the gameboy, and some support as the head of the ROM, etc.
You can download it here: gbhw.inc
also you will need an editor for your source code. I personally use VIM, and have defined a syntax file for assembler files .agb gameboy, if someone wants it can download it here: agb.vim , and install it in your ~ / .vim / syntax /. Remember to add the following line to your .vimrc that you recognize this extension:
au BufRead,BufNewFile *.agb set filetype=agb
Also I have a syntax file for .agb for Gedit files: agb.lang, you can install it in ~ / .local / share / gtksourceview-3.0 / language-specs (cread the directory if not exist)
If vim is too much for you (for now), you can try Notepad ++, which supports assembler. http://notepad-plus-plus.org/es/home
Also, to start testing your programs, it is best emulator. There are many GameBoy emulators available, although I recommend the BGB, works on both Windows and Linux using the Wine (in Options → Graphics, select DirectDraw or OpenGL output if you have problems with the image), and is truly faithful to the original machine, so you bear no surprises when you run your gameboy roms on a real, and different things work emulator. It also has an integrated debugger, displays tables tiles and sprites, disassembler ... cane. You can download it here: http://bgb.bircd.org/
Also works great VisualBoyAdvance GameBoy emulating, and the latest versions include GUI and some tools like power off graphic layers, sound channels, etc.
Let's make a small example program, a small hello world, that we can execute our gameboy, and show a little smiling face on screen. Then, step by step, I will explain the program's instructions. Let's do this:
First let's draw our sprite as we learned earlier:
.33333.. %01111100 %01111100 -- $7C $7C 3111113. %10000010 %11111110 -- $82 $FE 31.1.13. %10000010 %11010110 -- $82 $D6 31.1.13. %10000010 %11010110 -- $82 $D6 3111113. %10000010 %11111110 -- $82 $FE 3.111.3. %10000010 %10111010 -- $82 $BA 31...13. %10000010 %11000110 -- $82 $C6 .33333.. %01111100 %01111100 -- $7C $7C
Taking this data to draw our smiling face, we started with the program:
; Hello World ; David Pello 2010 ; ladecadence.net ; For the tutorial (In Spanish): ; http://wiki.ladecadence.net/doku.php?id=tutorial_de_ensamblador INCLUDE "gbhw.inc" ; import file definitions ; The Program Begins Here SECTION "start",HOME[$0100] nop jp inicio ; Head of the ROM (Macro defined in gbhw.inc) ; defines a rom without mapper, without 32K RAM, the basics ; (Such as tetris) ROM_HEADER ROM_NOMBC, ROM_SIZE_32KBYTE, RAM_SIZE_0KBYTE ; here begins are program inicio: nop di ; disables interrupts ld sp, $ffff ; We aim pile atop the ram inicializacion: ld a, %11100100 ; Palette colors from the darkest to ; Lighter, 11 10 01 00 ld [rBGP], a ; We write this in the palette register ld a, 0 ; write 0 records scroll in X and Y ld [rSCX], a ; positioned so that the visible screen ld [rSCY], a ; at the beginning (upper left) of the fund. call apaga_LCD ; We call the routine that turns off the LCD ; We load the tile in memory of tiles ld hl, TileCara ; HL loaded in the direction of our tile ld de, _VRAM ; address in the video memory ld b, 16 ; b = 16, number of bytes to copy .bucle_carga: ld a,[hl] ; A load in the data pointed to by HL ld [de], a ; and we put in the address pointed in DE dec b ; decrement b, b = b-1 jr z, .fin_bucle_carga ; if b = 0, finished, nothing left to copy inc hl ; We increased the read direction inc de ; We increased the write direction jr .bucle_carga ; we follow .fin_bucle_carga: ; We write our tile, tiles on the map ld hl, _SCRN0 ; HL in the direction of the background map ld [hl], $00 ; $00 = tile 0, our tile. ; configure and activate the display ld a, LCDCF_ON|LCDCF_BG8000|LCDCF_BG9800|LCDCF_BGON|LCDCF_OBJ8|LCDCF_OBJOFF ld [rLCDC], a ; infinite loop bucle: halt nop jr bucle ; LCD shutdown routine apaga_LCD: ld a,[rLCDC] rlca ; It sets the high bit of LCDC in the carry flag ret nc ; Display is already off, again. ; We VBlank hope to, because we can not turn off the screen ; some other time .espera_VBlank ld a, [rLY] cp 145 jr nz, .espera_VBlank ; we are in VBlank, we turn off the LCD ld a,[rLCDC] ;in A, the contents of the LCDC res 7,a ; we zero bit 7 (on the LCD) ld [rLCDC],a ; We wrote in the LCDC register content A ret ; volvemos ; Our tile data TileCara: DB $7C, $7C, $82, $FE, $82, $D6, $82, $D6 DB $82, $FE, $82, $BA, $82, $C6, $7C, $7C EndTileCara:
Well, there's our hello world. Save it as hola.agb, and compile using the bat or sh that you descargasteis follows: assemble.sh (or assemble.bat) hello hola.agb The same search for the file and ensablará and create the rom. Deberiais now have a hola.gb that if you execute on your emulator or gameboy, you will see a smiling face in the upper left corner of the screen. Well, actually, if the emulator is good, or you execute in real gameboy, it is likely that the screen is filled with these smileys except perhaps the Nintendo logo. Why ?, because we have not cleaned the video memory and other things with which we will improve the sample program.
Well, we split ...
; Hello World ; David Pello 2010 ; ladecadence.net ; for the tutorial(Spanish): ; http://wiki.ladecadence.net/doku.php?id=tutorial_de_ensamblador
Well, in assembly, the code comments starting with ";". As you know, the comments are for adding explanations and documentation to our code for others or us after a while, we know that such and such a routine schedule, etc. So simply, the assembler ignores the text of a line from a semicolon to the end of it.
INCLUDE "gbhw.inc" ; import file definitions
INCLUDE, assembler is a command that you do is import the file code word for it, from the time this command appears; that is, it is as if we took the code in "gbhw.inc" and then beat her. This is useful, because we can have code that used the same spot separate files, and import from all our programs, thus saving type them or copy each. As we see later in the line, a comment appears, so from ";" to the end of the line, this text is ignored.
; The program begins here SECTION "start",HOME[$0100] nop jp inicio
SECTION defines a section of code, as its name suggests, a block of code you want to define and place in a specific memory location. The assembler RGBDS allows us to define him a name "start" and a start address, address from which, put that code in memory. In the GameBoy, we have seen in the memory map, the ROM cartridge, where to go our own code, starts at $ 0000, but the first 256 bytes ($ 100) are reserved for interrupt routines, so we placing our code from the address $ 0100, so we put the "start" section from HOME ($ 0000) + $ 100. Then we executed a nop, GBZ80 this instruction does nothing, literally (no operation). It serves to waste time (4 cycles) or data blocks to fill gaps that need (1 byte). Then execute the instruction jp, used to make jumps from one place to another program, you can define conditions for the jump or not. In this case, no conditions, and we say that simply jump to the label Start. Why do we do this? Well, if we look at the documentation on organizazión a ROM GameBoy, we see that the head of the ROM should start in the $ 0104 address, so if we put a nop (1 byte), then jp
command ( 3 bytes), we are filling 4 bytes, just what we need to begin defining the header, which will introduce the next thing. But we need that jp, to jump from here, to begin to execute code as the header data will not executable code.; Head of the ROM (Macro defined in gbhw.inc) ; defines a rom without mapper, without 32K RAM, the basics ; (Such as tetris) ROM_HEADER ROM_NOMBC, ROM_SIZE_32KBYTE, RAM_SIZE_0KBYTE
ROM_HEADER This is a macro defined in the gbhw.inc. Macros are special constructions RGBDS that allow us to define certain blocks of code and modify using parameters. We can assume that with this, the RGBDS introduce us here the necessary bytes of the header of the cartridge, without having to worry about defining them by hand. You can take a look at the end of gbhw.inc if you have curiosity.
; Here begins our program inicio: nop di ; disables interrupts ld sp, $ffff ; We aim pile atop the ram
Well, we start with the topic. First a nop, we already know, then with the di instruction, we disable interrupts, not going to use in this example, and only bother us. Then we loaded in the SP register stack pointer, the address $FFFF which is the top of the ram of the gameboy. The stack is used to store temporary data we need, as the return addresses when a subroutine call. Furthermore, the stack grows downward directions. So now, if we keep a byte on the stack, SP is decremented, which is worth $fffe and the byte is stored there. If now we take a byte of the stack, it returns the data in $ fffe and increases SP, again pointing to $ffff.
inicializacion: ld a, %11100100 ; Palette colors from the darkest to ; Lighter, 11 10 01 00 ld [rBGP], a ; We write this in the palette register
We define a label "initialization" and started writing the code that will put our gameboy to work properly. First palette. As we know the classic GameBoy has 4 colors, white, black and two shades of gray. We can define two paddles, come on, order these four colors as you want, for one to be the first, another the second, etc. We also have two possible palettes. This is fine, because we also have such an inverted paddle effects or things like that. But for now we will use only the lightest (00) very average palette, darker colors (11). Then with the ld instruction, one of the most important and most usareis Gameboy assembly (basically because it serves to move data from one side to another of many possible ways), we loaded in the "a", the palette data (% 11100100 in binary). The ld instruction, is built basically as follows, ld
Now with another ld, loaded in the memory address pointed to by rBGP (gbhw.ic if we look, we see that rBGP is a normal memory address), the contents of register "a"; rBGP bracketed because what we mean is that stored in that memory address.
We will explain this: If we have the record "a", contains the number 200, and we "ld a, 5", now "a" contains 5. But if instead we "ld [a], 5" I we are doing is recorded in the memory location pointed to by "a", the value 5, which would write 5 in the memory 200 (which was the value it had "a" at the beginning).
ld a, 0 ; write 0 records scroll in X and Y ld [rSCX], a ; positioned so that the visible screen ld [rSCY], a ; at the beginning (upper left) of the fund.
Well, right ?, simple as we have seen, simply load the registry to 0 (a = 0) and then to write the content, records vertical and horizontal scroll, which are now worth 0, positioning the screen top left of the background map. Notice that we use the brackets, to refer to, writes this data in the memory location pointed to by RSCX and rSCY.
call apaga_LCD ; We call the routine that turns off the LCD
Here to call, call a subroutine. Assembler subroutines are the functions in C or other programming language. They are blocks of code that perform specific operations and when they finish, they return to where they were called. The assembly will seek a label called apagaLCD, jump there, and execute the code that is then until you find the ret instruction, which returns to the point where the subroutine was called. Unlike in C or other languages, if we want to pass parameters to subroutines, we have to do it ourselves, worrying about keeping certain data in registers or on the stack before calling the subroutine, and be careful not to undesirably change records we are using outside the subroutine, a common mistake that can give us good headaches.
; We load the tile in memory of tiles ld hl, TileCara ; HL loaded in the direction of our tile ld de, _VRAM ; address in the video memory ld b, 16 ; b = 16, numero de bytes a copiar .bucle_carga: ld a,[hl] ; A load in the data pointed to by HL ld [de], a ; and we put in the address pointed in DE dec b ; decrement b, b = b-1 jr z, .fin_bucle_carga ; if b = 0, finished, nothing left to copy inc hl ; We increased the write direction inc de ; We increased the write direction jr .bucle_carga ; We Follow .fin_bucle_carga:
Well, come with something interesting, since we will show how things are done especially loops in assembly. With this code we will load into memory tiles, the 16 bytes that define our smiling face. We could do so simply by loading instructions ld byte by byte ... we would load a byte to for example, and then would load the contents of a, a memory address of the start of memory tiles, then the next, etc ... but imagine we have to charge 50 tiles ... as not. For loops that are, as in any programming language. Let's see how we do.
First, we start by initializing certain data records. We charge in hl (log 16 bit), the address where the bytes are our tile. If we use a label in a ld instruction, the assembler replaces it with the memory address where the code or data after the label begins. We have to use a 16-bit register, because as we have seen, GBZ80 memory addresses are 16 bits.
After the registration charge, the direction of video memory, which starts at $ 8000 and as we have seen, is the starting address of the memory of tiles.
And now loaded in the register b, the number 16, which are the bytes that have to be copied.
Now we start with the loop. We put a label at the beginning of the loop, because we have to go back to it at each "round" of the loop. Well, carry on to the data pointed to by hl, hl as we know is the address where the data from our sprite begin. So with this instruction, we carry in to the first byte of our tile. Now get into the address pointed to by the contents of a, with what we already have the first byte of our tile, copied to the first memory address of tiles. Now we decrease b, using instruction
And now comes the main logic loop. Jr z, .fin_bucle_carga, what we do is, "jumps .fin_bucle_carga if the result of the previous operation was zero." As we saw in the section flags, z zero meant that the flag had been activated. So if the result of the above operation, dec b is zero, we jumped out of the loop, because we have finished copying. It is simple, at each turn of the loop, let ab subtracting one, so when we copied the 16 bytes, b will be zero, the flag and the z jz is activated, it will jump out.
If the zero flag is not set, because b still is not zero, then the jump does not occur, and the execution follows. Now with inc, we increase (add one) and HL. We do this, because as we have already copied the first data to the first memory address of tiles, now we have to continue with the second, third, etc., so that each time through the loop, we increased these two directions, pointing to Iran following directions of our data and useful memories positions respectively. And finally, we jump to .bucle_carga to copy the following until b is zero, which will jump to .fin_bloque_carga, finishing our loop.
; We write our tile, tiles on the map ld hl, _SCRN0 ;HL in the direction of the background map ld [hl], $00 ; $00 = tile 0, our tile.
This is very simple compared to this. Simply load in the direction hl map tiles background, and then write in this direction, the zero byte, indicating that the 0.0 tile map background, tile is zero, the first tile of the table, which as you know, just we write on the board tiles.
; configure and activate the display ld a, LCDCF_ON|LCDCF_BG8000|LCDCF_BG9800|LCDCF_BGON|LCDCF_OBJ8|LCDCF_OBJOFF ld [rLCDC], a
Nothing, we've got the data from our tile at the beginning of the memory of tiles, so is the zero tile, and have written a zero in the first position of the background map, which the first tile background ( 0.0 top left) corner is our sprite. So now we can turn the LCD to see our creation. What we do is upload to, lcd certain configurations, which are defined in the gbhw.inc. making an OR (|) between these settings, select all, LCDCF_ON (LCD on), LCDCF_BG8000 (tile data in $ 8000), LCDCF_BG9800 (tile map in $ 9,800), LCDCF_BGON (on background) LCDCF_OBJ8 (sprites 8 × 8) , LCDCF_OBJOFF (sprites off). And then we wrote in the memory of the control register LCD, which apply that configuration. From here, we would have our tile on the screen.
; infinite loop bucle: halt nop jr bucle
Well, in assembly, we arrived at an impasse, we must make an infinite loop. Why ?, because if not, he would continue processor running code that we have not written, let the following memory locations after our code, we do not know containing (typically random values), so anything could happen. So far, our program has ended, and we will not do anything else, we created this infinite loop that runs until we turn the console or the batteries run out
It's simple, make a halt, putting the processor in low power mode and stops executing until an interrupt occurs. As might get interruptions, because then we do a nop (nothing) and jump to the beginning of the loop again, so we could stay here forever.
; LCD shutdown routine apaga_LCD: ld a,[rLCDC] rlca ;It sets the high bit of LCDC in the carry flag ret nc ; Display is already off, again. ; We VBlank hope to, because we can not turn off the screen ; some other time .espera_VBlank ld a, [rLY] cp 145 jr nz, .espera_VBlank ; we are in VBlank, turn off the LCD ld a,[rLCDC] ; in A, the contents of the LCDC res 7,a ; we zero bit 7 (on the LCD) ld [rLCDC],a ; We wrote in the LCDC register content A ret ; return
Well, our program has ended, but then we can write data or subroutines that can be called or used by your code if the main program makes them reference system. This is the case of routine apaga_LCD, we had called from the main program with the call command.
Let's analyze it. The first thing we do is to upload the contents of LCD control register, rLCDC. Now we run the rclr order. It is a peculiar order, making it is to bit 7 of registration in the carry flag. For what it this? as for checks, of course, it is what we do next. As we mentioned, the ret instruction, exits the subroutine and returns execution to where he was allowed to call, but ret. You can also accept conditions to return or not, depending upon a flag.In this case we check the carry flag, if it is exactly zero. ret nc, it means "return if the carry flag is zero". In the carry flag we had set bit 7 of rLCDC, and if we look which means that bit in the video section, we see that means if the LCD is on or off. What will you see, we could then translate as "returns if the LCD is off," Come on, that does not make sense off the LCD if it is already off, therefore, if it is already back and our subroutine does nothing.
If it was not off, the bit 7 of rLCDC would be worth 1 and therefore the ret not be executed, so that the execution would continue. Okay, now, as we saw, it was very important not to turn the LCD while you were not in the vertical interval, as this may damage. So it's what we do now. RLY check the registry to see what is drawing the line LCD, if it is 145, it means you're out of the screen in the vertical interval, and we can continue. If not, we wait until rLY is 145.
Now turn off the LCD, load the contents of rLCDC in to, and the instruction must beef , which resets (resets) the bit to let him log, resets the bit 7, which as we know turned on or off LCD. Now we rewrite the content of a in rLCDC, so we changed the bit 7 without affecting the rest of the content of rLCDC.
The LCD is turned off, so that we can return to ret.
; Our tile data TileCara: DB $7C, $7C, $82, $FE, $82, $D6, $82, $D6 DB $82, $FE, $82, $BA, $82, $C6, $7C, $7C EndTileCara:
And here, then we write the data from our sprite.It is common to put data between labels significant, and also add a label to the end. Now we know we have 16 tiles, but if we had many, instead of counting them, we could tell the assembler to load EndTileCara-TileCara bytes, so that the assembler would count how many bytes between these addresses and would replace that value in our program. So it is well accustomed to this.
As you see, we put our data which such sprite, separated by commas, and predicted by the DB directive, Byte Defines which simply inserts those values in the memory of our program as is, to use as data.
For things how are you, you said it was important the infinite loop at the end of our program, because otherwise he would follow program running anything that could be next, and could interpret graphics data, music, or text, as if they were instructions, so anything could happen
And even here our example program, that, as you have seen their small mistakes (on purpose), so you can see to be initialized good memories.
So now, let's apply a couple of improvements to our program, I explain what I do, then good for him I commented estudieis understand code and treat them as I leave.First, I will define another tile, tile zero, it will be a totally white tile, and with it, I'll fill the background map before doing anything. Whereby, as will suppose, I will remove all the blank screen. After simply I will change the tile that will draw to 0.0 by the tile 1, because now our happy face will be the second tile, and draw it, otherwise the program will be the same.
I leave the code:
; Hello world improved ; David Pello 2010 ; ladecadence.net ; For The tutorial(Spanish): ; http://wiki.ladecadence.net/doku.php?id=tutorial_de_ensamblador INCLUDE "gbhw.inc" ; import file definitions ; The program begins here: SECTION "start",HOME[$0100] nop jp inicio ; Head of the ROM (Macro defined in gbhw.inc) ; defines a rom without mapper, without 32K RAM, the basics ; (Such as tetris) ROM_HEADER ROM_NOMBC, ROM_SIZE_32KBYTE, RAM_SIZE_0KBYTE ; Here begins our program inicio: nop di ; disables interrupts ld sp, $ffff ; We aim pile atop the ram inicializacion: ld a, %11100100 ; Palette colors from the darkest to ; Lighter, 11 10 01 00 ld [rBGP], a ; We write this in the palette register ld a, 0 ; write 0 records scroll in X and Y ld [rSCX], a ; whereby the visible screen positioned ld [rSCY], a ; at the beginning (upper left) of the fund. call apaga_LCD ; We call the routine that turns off the LCD ; We load the tiles in memory of tiles ld hl, Tiles ; HL loaded in the direction of our tile ld de, _VRAM ; address in the video memory ld b, 32 ; b = 32, number of bytes to copy (2 tiles) .bucle_carga: ld a,[hl] ; A load in the data pointed to by HL ld [de], a ; and we put in the address pointed in DE dec b ; decrement b, b = b-1 jr z, .fin_bucle_carga ; if b = 0, finished, nothing left to copy inc hl ; We increased the read direction inc de ; We increased the write direction jr .bucle_carga ; we follow .fin_bucle_carga: ; We clean the screen (fill entire background map), with tile 0 ld hl, _SCRN0 ld de, 32*32 ; number of tiles on the background map .bucle_limpieza: ld a, 0 ; tile 0 is our empty tile ld [hl], a dec de ; Now I have to check if 'from' is zero, to see if I have it ; finishes copying. DEC does not modify any flag, so I can not ; check the zero flag directly, but to 'of' zero, dye ; They must be zero two, so I can make or including ; and if the result is zero, both are zero. ld a, d ; d loaded in to or e ; and make a or e jp z, .fin_bucle_limpieza ; if d or e is zero, it is zero. We ended. inc hl ; We increased the write direction jp .bucle_limpieza .fin_bucle_limpieza ; Well, we have all the map tiles filled with tile 0, ;We can now paint ours ; We write our tile, tiles on the map ld hl, _SCRN0 ; HL in the direction of the background map ld [hl], $01 ; $01 = the tile 1, Our tile. tile. ; configure and activate the display ld a, LCDCF_ON|LCDCF_BG8000|LCDCF_BG9800|LCDCF_BGON|LCDCF_OBJ8|LCDCF_OBJOFF ld [rLCDC], a ; infinite loop bucle: halt nop jr bucle ; LCD shutdown routine apaga_LCD: ld a,[rLCDC] rlca ; It sets the high bit of LCDC in the carry flag ret nc ; Display is already off, again. ; We VBlank hope to, because we can not turn off the screen ; some other time .espera_VBlank ld a, [rLY] cp 145 jr nz, .espera_VBlank ; we are in VBlank, turn off the LCD ld a,[rLCDC] ; in A, the contents of the LCDC res 7,a ; we zero bit 7 (on the LCD) ld [rLCDC],a ; We wrote in the LCDC register content A ret ; return ; Our tiles Facts Tiles: DB $00, $00, $00, $00, $00, $00, $00, $00 DB $00, $00, $00, $00, $00, $00, $00, $00 DB $7C, $7C, $82, $FE, $82, $D6, $82, $D6 DB $82, $FE, $82, $BA, $82, $C6, $7C, $7C EndTiles:
And the result, this time if:
Well, now let's change the hello world, to create and manage a sprite. First, I will create another than white tile, tile will be a soft background, so you can see as the sprite moves over smoothly.Then I'll do this, I will initiate the gameboy, and hello world, but adding a palette also for sprites (I'll use it), I will fill the background with a soft tile, and then I'll create a sprite, and go moving bounced around the screen . I will use a small delay routine so that everything goes slower. Let's see the code:
; Hello sprite ; David Pello 2010 ; ladecadence.net ; For the tutorial(spanish): ; http://wiki.ladecadence.net/doku.php?id=tutorial_de_ensamblador INCLUDE "gbhw.inc" ; We define constants to work with our sprite ; We define constants to work with our sprite _SPR0_Y EQU _OAMRAM ; sprite Y 0 is the beginning of the sprite mem _SPR0_X EQU _OAMRAM+1 _SPR0_NUM EQU _OAMRAM+2 _SPR0_ATT EQU _OAMRAM+3 ; We create a couple of variables to see where we need to move the sprite _MOVX EQU _RAM ; start of RAM for data dispobible _MOVY EQU _RAM+1 ; The program begins here: SECTION "start",HOME[$0100] nop jp inicio ; Head of the ROM (Macro defined in gbhw.inc) ; defines a rom without mapper, without 32K RAM, the basics ; (Such as tetris) ROM_HEADER ROM_NOMBC, ROM_SIZE_32KBYTE, RAM_SIZE_0KBYTE ; Here begins our program inicio: nop di ; disables interrupts ld sp, $ffff ; We aim pile atop the ram inicializacion: ld a, %11100100 ; Palette colors from the darkest to ; Lighter, 11 10 01 00 ld [rBGP], a ; We write this in the background palette register ld [rOBP0], a ; and sprite palette 0 ld a, 0 ; write 0 records scroll in X and Y ld [rSCX], a ; positioned so that the visible screen ld [rSCY], a ; at the beginning (upper left) of the fund. call apaga_LCD ; We call the routine that turns off the LCD ; We load the tiles in memory of tiles ld hl, Tiles ; HL loaded in the direction of our tile ld de, _VRAM ; address in the video memory ld b, 32 ; b = 32, number of bytes to copy (2 tiles) .bucle_carga: ld a,[hl] ; A load in the data pointed to by HL ld [de], a ; y and we put in the address pointed in DE dec b ;decrement b, b = b-1 jr z, .fin_bucle_carga ; if b = 0, finished, nothing left to copy inc hl ; We increased the read direction inc de ; We increased the write direction jr .bucle_carga ; we follow .fin_bucle_carga: ; We clean the screen (fill entire background map), with tile 0 ld hl, _SCRN0 ld de, 32*32 ;number of tiles on the background map .bucle_limpieza: ld a, 0 ; tile 0 is our empty tile ld [hl], a dec de ; Now I have to check if it is zero, to see if I have it ; finishes copying. dec ningñun flag does not change, so I can not ; check the zero flag directly, but that is zero, dye ; They must be zero two, so I can make or including ; and if the result is zero, both are zero. ld a, d ; d loaded in to or e ; and make a or e jp z, .fin_bucle_limpieza ; if d or e is zero, it is zero. We ended. inc hl ; We increased the write direction jp .bucle_limpieza .fin_bucle_limpieza ; Well, we have all the map tiles filled with tile 0, ; Now we will create the sprite. ld a, 30 ld [_SPR0_Y], a ;Y position of the sprite ld a, 30 ld [_SPR0_X], a ; X position of the sprite ld a, 1 ld [_SPR0_NUM], a ; number of tile on the table that we will use tiles ld a, 0 ld [_SPR0_ATT], a ; special attributes, so far nothing. ; configure and activate the display ld a, LCDCF_ON|LCDCF_BG8000|LCDCF_BG9800|LCDCF_BGON|LCDCF_OBJ8|LCDCF_OBJON ld [rLCDC], a ; We prepare animation variables ld a, 1 ld [_MOVX], a ld [_MOVY], a ; infinite loop animacion: ; first, we wait for the VBlank, since we can not change ; VRAM out of it, or weird things will .wait ld a, [rLY] cp 145 jr nz, .wait ; incrementamos las y ld a, [_SPR0_Y] ; And we load the current position of the sprite ld hl, _MOVY ; hl in the direction of increasing Y add a, [hl] ; add ld hl, _SPR0_Y ld [hl], a ; keep ; compared to see if they change the direction cp 152 ; so you do not exit the screen (max Y) jr z, .dec_y cp 16 jr z, .inc_y ; the same minimum coordinate Y = 16 ; do not change jr .end_y .dec_y: ld a, -1 ; now we have to decrease the Y ld [_MOVY], a jr .end_y .inc_y: ld a, 1 ; now we have to increase the Y ld [_MOVY], a .end_y: ; we go with the X, the same but changing the margins ld a, [_SPR0_X] ; We load the current X position of the sprite ld hl, _MOVX ; hl, the incrementing direction X add a, [hl] ; add ld hl, _SPR0_X ld [hl], a ; keep ; compared to see if they change the direction cp 160 ; so you do not exit the screen (max X) jr z, .dec_x cp 8 ; the same minimum coord left = 8 jr z, .inc_x ; do not change jr .end_x .dec_x: ld a, -1 ; now we have to decrease the X ld [_MOVX], a jr .end_x .inc_x: ld a, 1 ; now we have to increase the X ld [_MOVX], a .end_x: ; a small delay call retardo ; we start jr animacion ; LCD shutdown routine apaga_LCD: ld a,[rLCDC] rlca ; It sets the high bit of LCDC in the carry flag ret nc ; Display is already off, again. ; We VBlank hope to, because we can not turn off the screen ; some other time .espera_VBlank ld a, [rLY] cp 145 jr nz, .espera_VBlank ; we are in VBlank, we turn off the LCD ld a,[rLCDC] ; in A, the contents of the LCDC res 7,a ; we zero bit 7 (on the LCD) ld [rLCDC],a ; We wrote in the LCDC register content A ret ; return ; delay routine retardo: ld de, 2000 ; number of times to execute the loop .delay: dec de ; decrement ld a, d ; see if zero or e jr z, .fin_delay nop jr .delay .fin_delay: ret ; Our tiles Facts Tiles: DB $AA, $00, $44, $00, $AA, $00, $11, $00 DB $AA, $00, $44, $00, $AA, $00, $11, $00 DB $3E, $3E, $41, $7F, $41, $6B, $41, $7F DB $41, $63, $41, $7F, $3E, $3E, $00, $00 EndTiles:
The result:
Well, let's create an example for reading the Pad and buttons. If we go to great PanDocs and its section on the pad, (http://gbdev.gg8.se/wiki/articles/Joypad_Input), we see that we have a record mapped memory, $ FF00, we will call rP1 ( of Player 1), we can read to know the state of the pad and buttons. Well, I guess to save pins on the CPU of the gameboy, the designers decided to implement a matrix type layout for the buttons on the GameBoy, so the directional pad (4 buttons), and A, B, Select, buttons Start, share the input lines. So how can we know which is pressed ?. For as we see in the PanDocs, there are two bits in this register, allowing us to "activate" or pad and buttons, and then read them. Let's see the log:
Bit 7 - Sin uso Bit 7 - Not used Bit 6 - Not used Bit 5 - P15 Select buttons (0 = Select) Bit 4 - P14 Select the directional pad (0 = Select) Bit 3 - P13 Down or Start (0 = Down) (Read Only) Bit 2 - P12 Up or Select (0 = Down) (Read Only) Bit 1 - P11 Left or B (0 = Down) (Read Only) Bit 0 - P10 Right or A (0 = Down) (Read Only)
So how are we going to do to read all the buttons? Well, simple, let's create a routine that will do is:
Then we will check this variable to find out what buttons are pressed (the bits that are to 0) and move the sprite according to the buttons. We will also switch between the two possible palettes sprites when you press A.
One thing to consider is the "bouncing". The electrical contacts of the buttons of each button, can suffer from the effect known as "bouncing" that is, at the microscopic level when you press a button, contact bounce producing small jumps in the signal, so that a small space of time, the signal is not real, and you can read that a button is not pressed, as if it is, or that the button is pressed when you just let go.Modern appliances, usually have circuitry that controls this bouncing, but not the GameBoy. The solution is simple, make a few readings followed, to try to minimize this effect.
I've also added code to clear the memory attribute sprites, to put the 40 sprites, all at zero, so the sprites unused, will be off the screen. Perhaps it emulators nothing happens, but in real GameBoy, I've had a couple of sprites with "junk" appeared from the middle of the screen. You never know what can hold the RAM chips on startup (static and so on), so remember, always initialize.
Then let the program:
; Hello joypad ; David Pello 2010 ; ladecadence.net ; For the tutorial(Spanish): ; http://wiki.ladecadence.net/doku.php?id=tutorial_de_ensamblador INCLUDE "gbhw.inc" ; import file definitions ; We define constants to work with our sprite _SPR0_Y EQU _OAMRAM ; sprite Y 0 is the beginning of the sprite mem _SPR0_X EQU _OAMRAM+1 _SPR0_NUM EQU _OAMRAM+2 _SPR0_ATT EQU _OAMRAM+3 ;variable to save the state of the pad _PAD EQU _RAM ; at the beginning of the internal RAM ; The program begins here: SECTION "start",HOME[$0100] nop jp inicio ; Head of the ROM (Macro defined in gbhw.inc) ; defines a rom without mapper, without 32K RAM, the basics ; (Such as tetris) ROM_HEADER ROM_NOMBC, ROM_SIZE_32KBYTE, RAM_SIZE_0KBYTE ; Here begins are program inicio: nop di ; disables interrupts ld sp, $ffff ; We aim pile atop the ram inicializacion: ld a, %11100100 ; Palette colors from the darkest to ; lighter, 11 10 01 00 ld [rBGP], a ; We write this in the background palette register ld [rOBP0], a ; and sprite palette 0 ; create another palette to the palette 2 sprites, reverse to normal ld a, %00011011 ld [rOBP1], a ld a, 0 ; write 0 records scroll in X and Y ld [rSCX], a ; positioned so that the visible screen ld [rSCY], a ; at the beginning (upper left) of the fund. call apaga_LCD ; We call the routine that turns off the LCD ; We load the tiles in memory of tiles ld hl, Tiles ; HL loaded in the direction of our tile ld de, _VRAM ; address in the video memory ld b, 32 ; b = 32, number of bytes to copy (2 tiles) .bucle_carga: ld a,[hl] ; load in the data pointed to by HL ld [de], a ; and we put in the address pointed in DE dec b ; decrement b, b = b-1 jr z, .fin_bucle_carga ; if b = 0, finished, nothing left to copy inc hl ; We increased the read direction inc de ; We increased the write direction jr .bucle_carga ; we follow .fin_bucle_carga: ; We clean the screen (fill entire background map), with tile 0 ld hl, _SCRN0 ld de, 32*32 ; number of tiles on the background map .bucle_limpieza_fondo: ld a, 0 ; tile 0 is our empty tile ld [hl], a dec de ; Now I have to check if it is zero, to see if I have it ; finishes copying. dec not modify any flag, so I can not ; check the zero flag directly, but that is zero, dye ; They must be zero two, so I can make or including ; and if the result is zero, both are zero. ld a, d ; d loaded in to or e ; and make a or e jp z, .fin_bucle_limpieza_fondo ; if d or e is zero, it is zero. ; Ended. inc hl ; We increased the write direction jp .bucle_limpieza_fondo .fin_bucle_limpieza_fondo ; Well, we have all the map tiles filled with tile 0 ; We clean the memory of sprites ld hl, _OAMRAM ; sprite attribute memory ld de, 40*4 ; 40 sprites x 4 bytes each .bucle_limpieza_sprites ld a, 0 ; we will start fresh, so the sprites ld [hl], a ; unused, will be offscreen dec de ; as in previous loop ld a, d ; d loaded in to or e ; and make a or e jp z, .fin_bucle_limpieza_sprites ; if d or e is zero, it is zero. inc hl ; We increased the write direction jp .bucle_limpieza_sprites .fin_bucle_limpieza_sprites ; Now we will create the sprite. ld a, 74 ld [_SPR0_Y], a ; Y position of the sprite ld a, 90 ld [_SPR0_X], a ; X position of the sprite ld a, 1 ld [_SPR0_NUM], a ; number of tile on the table that we will use tiles ld a, 0 ld [_SPR0_ATT], a ; special attributes, so far nothing. ; configure and activate the display ld a, LCDCF_ON|LCDCF_BG8000|LCDCF_BG9800|LCDCF_BGON|LCDCF_OBJ8|LCDCF_OBJON ld [rLCDC], a ; main loop movimiento: ; We read the pad call lee_pad ; first, we wait for the VBlank, since we can not change ; VRAM out of it, or weird things will .wait: ld a, [rLY] cp 145 jr nz, .wait ; Now we move the sprite depending on the buttons ld a, [_PAD] ; We charge status pad and %00010000 ; right call nz, mueve_derecha ; if the result is not zero, there had 1 ; then we call the subroutine ld a, [_PAD] and %00100000 ; left call nz, mueve_izquierda ld a, [_PAD] and %01000000 ; above call nz, mueve_arriba ld a, [_PAD] and %10000000 ; down call nz, mueve_abajo ld a, [_PAD] and %00000001 ; A button call nz, cambia_paleta ; a small delay call retardo ; we start jr movimiento ; Movement routines mueve_derecha: ld a, [_SPR0_X] ; We get the current position cp 160 ; We are on the corner? ret z ; if we are in the corner, back inc a ; move ld [_SPR0_X], a ; We keep the position ret ; return mueve_izquierda: ld a, [_SPR0_X] ; We get the current position cp 8 ; We are on the corner? ret z ; if we are in the corner, back dec a ; go back ld [_SPR0_X], a ; We keep the position ret mueve_arriba: ld a, [_SPR0_Y] ; We get the current position cp 16 ; We are on the corner? ret z ; if we are in the corner, back dec a ; go back ld [_SPR0_Y], a ; We keep the position ret mueve_abajo: ld a, [_SPR0_Y] ; We get the current positionl cp 152 ; We are on the corner? ret z ; if we are in the corner, back inc a ; move ld [_SPR0_Y], a ; We keep the position ret cambia_paleta: ld a, [_SPR0_ATT] and %00010000 ; in bit 4, is the number of palette jr z, .paleta0 ; If zero was selected palette 0 ; if not, was selected blade 1 ld a, [_SPR0_ATT] res 4, a ; we zero bit 4, selecting the palette 0 ld [_SPR0_ATT], a ; We keep attributes call retardo ; the change is very fast, we will wait a bit ret ; return .paleta0: ld a, [_SPR0_ATT] set 4, a ; We put one bit 4, selecting blade 1 ld [_SPR0_ATT], a ; We keep attributes call retardo ret ; return ; Routine reading pad lee_pad: ; we will read the Cruzeta: ld a, %00100000 ; bit 4-0, 5-1 bit (on Cruzeta, no buttons) ld [rP1], a ; now we read the status of the Cruzeta, to avoid bouncing ; We do several readings ld a, [rP1] ld a, [rP1] ld a, [rP1] ld a, [rP1] and $0F ; only care about the bottom 4 bits. swap a ; lower and upper exchange. ld b, a ; We keep Cruzeta status in b ; we go for the buttons ld a, %00010000 ; bit 4 to 1, bit 5 to 0 (enabled buttons, not Cruzeta) ld [rP1], a ; read several times to avoid bouncing ld a, [rP1] ld a, [rP1] ld a, [rP1] ld a, [rP1] ; we at A, the state of the buttons and $0F ; only care about the bottom 4 bit or b ; or make a to b, to "meter" in Part ; A superior, Cruzeta status. ; we now have at A, the state of all, we complement and ; store it in the variable cpl ld [_PAD], a ; return ret ; LCD shutdown routine apaga_LCD: ld a,[rLCDC] rlca ; It sets the high bit of LCDC in the carry flag ret nc ; Display is already off, again. ; We VBlank hope to, because we can not turn off the screen ; some other time .espera_VBlank ld a, [rLY] cp 145 jr nz, .espera_VBlank ; we are in VBlank, turn off the LCD ld a,[rLCDC] ;in A, the contents of the LCDC res 7,a ; we zero bit 7 (on the LCD) ld [rLCDC],a ; We wrote in the LCDC register content A ret ; return ; rdelay routine retardo: ld de, 2000 ; number of times to execute the loop .delay: dec de ; decrement ld a, d ; see if zero or e jr z, .fin_delay nop jr .delay .fin_delay: ret ; Our tiles Facts Tiles: DB $AA, $00, $44, $00, $AA, $00, $11, $00 DB $AA, $00, $44, $00, $AA, $00, $11, $00 DB $3E, $3E, $41, $7F, $41, $6B, $41, $7F DB $41, $63, $41, $7F, $3E, $3E, $00, $00 EndTiles:
Now in this example, we will make use of the window, like a typical screens "start" with information in some games. Games like Zelda, use this technique. Simply load the other map tiles (remember that there were two maps, one for $ 9800- $ 9BFF, and another $ 9C00- $ 9FFF), with the tiles you want to show in the window, and say to the window, Use this map to the window prevented from starting the rLCDC. In addition, for this example, I added a lot of things. To start I created a map background with multiple tiles, like a screen of a game, and we can move across the width of the map, using the scroll records. Thus, when the sprite reaches a certain position, we will not let you move more, but we will move the scroll to make it appear that continues to move beyond the screen. Also I will use a 16 × 16 sprite. How?For simple sprites using 4 8 × 8 together, and moving simultaneously when necessary. Also I will use the attributes of the sprites, making them a mirror move depending on whether left or right, so I can use the same tiles for both directions. Another trick I've used is to switch between three sets of sprites for the character, when you are moving. So it seems that your legs move. Num_spr_mario subroutines are responsible, and camina_mario. Besides the sin_pulsaciones routine returns to the quiet character sprite when nothing is pressed. In the code, you can see, I've added eg RellenaMemoria CopiaMemoria and subroutines. As was making multiple copies to fill in memory tiles, maps, clean sprites, etc, because I left them as subroutines, they expect the data in some records, so we can reuse them. I have also left aside the sprites in files, so I can make changes more easily. In the end, you see that use the includes two tags for deliminar data within each file.
And nothing, all this together, then:
; Hello Window ; David Pello 2010 ; ladecadence.net ; For the tutorial(Spanish): ; http://wiki.ladecadence.net/doku.php?id=tutorial_de_ensamblador INCLUDE "gbhw.inc" ; import file definitions ; We define constants to work with our sprites _SPR0_Y EQU _OAMRAM ; sprite Y 0 is the beginning of the sprite mem _SPR0_X EQU _OAMRAM+1 _SPR0_NUM EQU _OAMRAM+2 _SPR0_ATT EQU _OAMRAM+3 _SPR1_Y EQU _OAMRAM+4 _SPR1_X EQU _OAMRAM+5 _SPR1_NUM EQU _OAMRAM+6 _SPR1_ATT EQU _OAMRAM+7 _SPR2_Y EQU _OAMRAM+8 _SPR2_X EQU _OAMRAM+9 _SPR2_NUM EQU _OAMRAM+10 _SPR2_ATT EQU _OAMRAM+11 _SPR3_Y EQU _OAMRAM+12 _SPR3_X EQU _OAMRAM+13 _SPR3_NUM EQU _OAMRAM+14 _SPR3_ATT EQU _OAMRAM+15 ; VARIABLES ; variable to save the state of the pad _PAD EQU _RAM ; at the beginning of the internal RAM ; control variables sprites _POS_MAR_2 EQU _RAM+1 ; placing the second position where sprites _SPR_MAR_SUM EQU _RAM+2 ; I number to add to the sprites to alternate them ; The program begins here: SECTION "start",HOME[$0100] nop jp inicio ; Head of the ROM (Macro defined in gbhw.inc) ; defines a rom without mapper, without 32K RAM, the basics ; (Such as tetris) ROM_HEADER ROM_NOMBC, ROM_SIZE_32KBYTE, RAM_SIZE_0KBYTE ; Here begins our program inicio: nop di ; disables interrupts ld sp, $ffff ; We aim pile atop the ram inicializacion: ; We began variables ld hl, _POS_MAR_2 ; sprites looking to the right. ld [hl], -8 ld hl, _SPR_MAR_SUM ; We start with 0 ld [hl], 0 ; pallets ld a, %11100100 ; Palette colors from the darkest to ; Lighter, 11 10 01 00 ld [rBGP], a ; We write this in the background palette register ld [rOBP0], a ; and sprite palette 0 ; create another palette to the palette 2 sprites, Mario ld a, %11010000 ld [rOBP1], a ; scroll ld a, 0 ; write 0 records scroll in X and Y ld [rSCX], a ; positioned so that the visible screen ld [rSCY], a ; at the beginning (upper left) of the fund. ; video call apaga_LCD ; We call the routine that turns off the LCD ; We load the tiles in memory of tiles ld hl, Tiles ; HL loaded in the direction of our tile ld de, _VRAM ; address in the video memory ld bc, FinTiles-Tiles ; number of bytes to copy call CopiaMemoria ; We load the map ld hl, Mapa ld de, _SCRN0 ; map 0 ld bc, 32*32 call CopiaMemoria ; We load the map to the window ld hl, Ventana ld de, _SCRN1 ; Map 1 ld bc, 32*32 call CopiaMemoria ; well, we all loaded map tiles ; We clean the memory of sprites ld de, _OAMRAM ; sprite attribute memory ld bc, 40*4 ; 40 sprites x 4 bytes each ld l, 0 ; we will start fresh, so the sprites call RellenaMemoria ; Unused fall outside screen ; Now we will create sprites. ld a, 136 ld [_SPR0_Y], a ; Y position of the sprite ld a, 80 ld [_SPR0_X], a ; X position of the sprite ld a, 0 ld [_SPR0_NUM], a ; number of tile on the table that we will use tiles ld a, 16 | 32 ld [_SPR0_ATT], a ; special attributes, Pallet 1 ld a, 136+8 ld [_SPR1_Y], a ;Y position of the sprite ld a, 80 ld [_SPR1_X], a ;X position of the sprite ld a, 1 ld [_SPR1_NUM], a ; number of tile on the table that we will use tiles ld a, 16 | 32 ld [_SPR1_ATT], a ; special attributes, Pallet 1 ld a, 136 ld [_SPR2_Y], a ; Y position of the sprite ld a, [_POS_MAR_2] add 80 ld [_SPR2_X], a ; X position of the sprite ld a, 2 ld [_SPR2_NUM], a ; number of tile on the table that we will use tiles ld a, 16 | 32 ld [_SPR2_ATT], a ; special attributes, Pallet 1 ld a, 136+8 ld [_SPR3_Y], a ; Y position of the sprite ld a, [_POS_MAR_2] add a, 80 ld [_SPR3_X], a ; X position of the sprite ld a, 3 ld [_SPR3_NUM], a ; number of tile on the table that we will use tiles ld a, 16 | 32 ld [_SPR3_ATT], a ; special attributes, Pallet 1 ; configure and activate the display ld a, LCDCF_ON|LCDCF_BG8000|LCDCF_BG9800|LCDCF_BGON|LCDCF_OBJ8|LCDCF_OBJON|LCDCF_WIN9C00 ld [rLCDC], a ; main loop movimiento: ; We read the pad call lee_pad ; first, we wait for the VBlank, since we can not change ; VRAM out of it, or weird things will happen .wait: ld a, [rLY] cp 145 jr nz, .wait ; Now we move the sprite depending on the buttons ld a, [_PAD] ; We charge status pad and %00010000 ; right call nz, mueve_derecha ; if the result is not zero, there had 1 ; then we call the subroutine ld a, [_PAD] and %00100000 ; left call nz, mueve_izquierda ld a, [_PAD] and %01000000 ; above ;call nz, mueve_arriba ld a, [_PAD] and %10000000 ; down ;call nz, mueve_abajo ld a, [_PAD] and %00001000 ; START Button call nz, muestra_ventana ld a, [_PAD] and %11111111 call z, sin_pulsaciones ; ; a small delay ld bc, 2000 call retardo ; we start jr movimiento ; Movement routines mueve_derecha: ld a, [_SPR0_X] ; We get the current position cp 120 ; We are on the corner? jp nz, .ad ; if we are not around the corner, move ld a, [rSCX] ; if we are on the edge, we move the scroll inc a ld [rSCX], a ; modificamos los sprites call num_spr_mario call camina_mario ret .ad: ; the second sprites must be behind the first push af ld a, -8 ld [_POS_MAR_2], a pop af ; motion inc a ; move ld [_SPR0_X], a ; We keep the position ld [_SPR1_X], a ld hl, _POS_MAR_2 ; The first shift add a, [hl] ; add ld [_SPR2_X], a ; keep ld [_SPR3_X], a ; right, therefore sprites must be reflected horizontally ld a, [_SPR0_ATT] set 5, a ld [_SPR0_ATT], a ld [_SPR1_ATT], a ld [_SPR2_ATT], a ld [_SPR3_ATT], a ; sprites modify the call num_spr_mario call camina_mario ret ; return mueve_izquierda: ld a, [_SPR0_X] ; We get the current position cp 16 ; We are on the corner? jp nz, .ai ; if we are not around the corner, move ld a, [rSCX] ; if we are, scroll dec a ld [rSCX], a ; mmodify the sprites call num_spr_mario call camina_mario ret .ai: ; the second sprites must be before the first push af ld a, 8 ld [_POS_MAR_2], a pop af ; motion dec a ; go back ld [_SPR0_X], a ; We keep the position ld [_SPR1_X], a ld hl, _POS_MAR_2 ; the first shift add a, [hl] ; add ld [_SPR2_X], a ; keep ld [_SPR3_X], a ; left, therefore, sprite must be reflected horizontally ld a, [_SPR0_ATT] res 5, a ld [_SPR0_ATT], a ld [_SPR1_ATT], a ld [_SPR2_ATT], a ld [_SPR3_ATT], a ; modify the sprites call num_spr_mario call camina_mario ret ; return mueve_arriba: ld a, [_SPR0_Y] ; We get the current position cp 16 ; We are on the corner? ret z ; if we are in the corner, back dec a ; go back ld [_SPR0_Y], a ; We keep the position ret mueve_abajo: ld a, [_SPR0_Y] ; We get the current position cp 152 ; We are on the corner? ret z ; if we are in the corner, back inc a ; move ld [_SPR0_Y], a ; We keep the position ret muestra_ventana: ld a, 8 ld [rWX], a ld a, 144 ld [rWY], a ; Activate and deactivate the window sprites ld a, [rLCDC] or LCDCF_WINON res 1, a ld [rLCDC], a ; animation ld a, 144 .anim_most_vent: push af ld bc, 1000 call retardo pop af dec a ld [rWY], a jr nz, .anim_most_vent ; We hope to select pressed to exit .espera_salir: call lee_pad and %00001000 ; START button jr z, .espera_salir; .anim_ocul_vent: push af ld bc, 1000 call retardo pop af inc a ld [rWY], a cp 144 jr nz, .anim_ocul_vent ; Deactivate and activate the window sprites ld a, [rLCDC] res 5, a or LCDCF_OBJON ld [rLCDC], a ret ; volvemos ; if nothing is pressed, you come here to modify sprite by mario ; static sin_pulsaciones: ld hl, _SPR_MAR_SUM; We start with 0 ld [hl], 0 ld a, 0 ld [_SPR0_NUM], a inc a ld [_SPR1_NUM], a inc a ld [_SPR2_NUM], a inc a ld [_SPR3_NUM], a ret ; modify the variable to change mario sprites walking num_spr_mario: ld a, [_SPR_MAR_SUM] add 4 ; add 4 ld [_SPR_MAR_SUM], a ; we keep cp 12 ; lower than 12? ret nz ; return ld a, 4 ; 12? then back to 4 ld [_SPR_MAR_SUM], a ret camina_mario: ld a, [_SPR_MAR_SUM] ld [_SPR0_NUM], a inc a ld [_SPR1_NUM], a inc a ld [_SPR2_NUM], a inc a ld [_SPR3_NUM], a ret ; Routine reading pad lee_pad: ; we will read the Cruzeta: ld a, %00100000 ; bit 4-0, 5-1 bit (on Cruzeta, no buttons) ld [rP1], a ; now we read the status of the Cruzeta, to avoid bouncing ; We do several readings ld a, [rP1] ld a, [rP1] ld a, [rP1] ld a, [rP1] and $0F ; only care about the bottom 4 bits. swap a ; lower and upper exchange. ld b, a ; We keep Cruzeta status in b ; we go for the buttons ld a, %00010000 ; bit 4 to 1, bit 5 to 0 (enabled buttons, not Cruzeta) ld [rP1], a ; We read several times to avoid bouncing ld a, [rP1] ld a, [rP1] ld a, [rP1] ld a, [rP1] ; we at A, the state of the buttons and $0F ; only care about the bottom 4 bits. or b ; or make a to b, to "meter" in Part ; A superior, Cruzeta status. ; we now have at A, the state of all, we complement and ; store it in the variable cpl ld [_PAD], a ; volvemos ret ; LCD shutdown routine apaga_LCD: ld a,[rLCDC] rlca ; It sets the high bit of LCDC in the carry flag ret nc ; Display is already off, again. ; We VBlank hope to, because we can not turn off the screen ; some other time .espera_VBlank ld a, [rLY] cp 145 jr nz, .espera_VBlank ; we are in VBlank, we turn off the LCD ld a,[rLCDC] ; in A, the contents of the LCDC res 7,a ; we zero bit 7 (on the LCD) ld [rLCDC],a ; eWe wrote in the LCDC register content A ret ; return ; delay routine ; parameters ; bc - number of iterations retardo: .delay: dec bc ; decrement ld a, b ; see if zero or c jr z, .fin_delay nop jr .delay .fin_delay: ret ; memory copy routine ; copy a number of bytes from one direction to another ; expects the parameters: ; hl - copying data address ; of - destination address ; bc - number of data to be copied ; destroys the contents of A CopiaMemoria: ld a, [hl] ; To load the data in ld [de], a ; copy the data to the destination dec bc ; least one copy ; We check if bc is zero ld a, c or b ret z ; If zero, we return ; if not, we still inc hl inc de jr CopiaMemoria ; Fill routine memory ; filled a number of bytes of memory with a data ; expects the parameters: ; of - destination address ; bc - amount of data to fill ; l - data to fill RellenaMemoria: ld a, l ld [de], a ; ; puts the data in the destination dec bc ; least one fill ld a, c or b ; We check if bc is zero ret z ;If zero return inc de ; if not, we still jr RellenaMemoria Tiles: INCLUDE "mario_sprites.z80" FinTiles: Mapa: INCLUDE "mapa_mario.z80" FinMapa: Ventana: INCLUDE "ventana.z80" FinVentana:
And data,
mario_sprites.z80:
DB $00,$00,$0F,$0F,$1B,$14,$7F,$7F DB $1B,$14,$1B,$1E,$31,$2F,$3F,$3F DB $10,$1F,$1F,$1F,$3F,$2A,$1F,$15 DB $1F,$1F,$07,$07,$09,$0F,$0F,$0F DB $00,$00,$00,$00,$E0,$E0,$F0,$10 DB $F0,$F0,$30,$F0,$68,$F8,$88,$F8 DB $70,$F0,$F8,$C8,$F8,$68,$B8,$D8 DB $98,$F8,$F0,$F0,$10,$F0,$F0,$F0 DB $00,$00,$0F,$0F,$1B,$14,$7F,$7F DB $1B,$14,$1B,$1E,$31,$2F,$3F,$3F DB $10,$1F,$0F,$0F,$1E,$1B,$1E,$17 DB $1F,$1F,$0F,$0F,$11,$1F,$1F,$1F DB $00,$00,$00,$00,$E0,$E0,$F0,$10 DB $F0,$F0,$30,$F0,$68,$F8,$88,$F8 DB $70,$F0,$F0,$90,$F8,$48,$74,$FC DB $F4,$FC,$F4,$FC,$0C,$0C,$00,$00 DB $00,$00,$0F,$0F,$1B,$14,$7F,$7F DB $1B,$14,$1B,$1E,$31,$2F,$3F,$3F DB $10,$1F,$6F,$6F,$5F,$7A,$5F,$75 DB $5F,$7F,$7F,$7F,$00,$00,$00,$00 DB $00,$00,$00,$00,$E0,$E0,$F0,$10 DB $F0,$F0,$30,$F0,$68,$F8,$88,$F8 DB $70,$F0,$F8,$C8,$F8,$68,$B8,$D8 DB $98,$F8,$E8,$F8,$90,$F0,$E0,$E0 DB $00,$00,$00,$00,$00,$00,$00,$00 DB $00,$00,$00,$00,$00,$00,$00,$00 DB $FF,$00,$FF,$00,$FF,$00,$FF,$00 DB $FF,$00,$FF,$00,$FF,$00,$FF,$00 DB $FF,$FF,$FF,$00,$FF,$88,$FF,$00 DB $FF,$20,$FF,$02,$FF,$80,$FF,$22 DB $FF,$00,$C3,$3C,$81,$7E,$B9,$46 DB $81,$7E,$81,$7E,$9D,$62,$81,$7E DB $FF,$00,$FE,$01,$F8,$07,$F0,$0F DB $E0,$1F,$C0,$3F,$80,$7F,$00,$FF DB $FF,$00,$00,$FF,$00,$FF,$00,$FF DB $00,$FF,$00,$FF,$00,$FF,$00,$FF DB $FF,$00,$1F,$E0,$0F,$F0,$07,$F8 DB $03,$FC,$01,$FE,$01,$FE,$01,$FE DB $F8,$00,$97,$00,$6E,$00,$7D,$00 DB $7F,$00,$7F,$00,$BB,$00,$C4,$00 DB $33,$00,$CD,$00,$FE,$00,$FE,$00 DB $FA,$00,$F6,$00,$FD,$00,$03,$00 DB $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF DB $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF DB $FF,$FF,$FF,$FF,$FF,$FF,$E0,$E0 DB $EF,$EF,$EF,$EF,$EF,$EF,$EF,$EF DB $FF,$FF,$FF,$FF,$FF,$FF,$00,$00 DB $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF DB $FF,$FF,$FF,$FF,$FF,$FF,$07,$07 DB $F7,$F7,$F7,$F7,$F7,$F7,$F7,$F7 DB $F7,$F7,$F7,$F7,$F7,$F7,$F7,$F7 DB $F7,$F7,$F7,$F7,$F7,$F7,$F7,$F7 DB $F7,$F7,$F7,$F7,$F7,$F7,$F7,$F7 DB $07,$07,$FF,$FF,$FF,$FF,$FF,$FF DB $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF DB $00,$00,$FF,$FF,$FF,$FF,$FF,$FF DB $EF,$EF,$EF,$EF,$EF,$EF,$EF,$EF DB $E0,$E0,$FF,$FF,$FF,$FF,$FF,$FF DB $EF,$EF,$EF,$EF,$EF,$EF,$EF,$EF DB $EF,$EF,$EF,$EF,$EF,$EF,$EF,$EF DB $FF,$FF,$BD,$BD,$BD,$BD,$DB,$DB DB $DB,$DB,$E7,$E7,$E7,$E7,$FF,$FF DB $FF,$FF,$83,$83,$BF,$BF,$8F,$8F DB $BF,$BF,$BF,$BF,$83,$83,$FF,$FF DB $FF,$FF,$9D,$9D,$AD,$AD,$AD,$AD DB $B5,$B5,$B5,$B5,$B9,$B9,$FF,$FF DB $FF,$FF,$83,$83,$EF,$EF,$EF,$EF DB $EF,$EF,$EF,$EF,$EF,$EF,$FF,$FF DB $FF,$FF,$E7,$E7,$DB,$DB,$DB,$DB DB $81,$81,$BD,$BD,$BD,$BD,$FF,$FF
mapa_mario.z80
DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$13,$14,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$13,$14,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$13,$14,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$13,$14 DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$13,$14,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0F,$0D,$10,$11 DB $11,$12,$0D,$0F,$0D,$0D,$0D,$10,$11,$12 DB $0D,$0D,$0D,$0D,$0F,$0D,$0F,$0D,$0D,$10 DB $11,$11,$12,$0D,$0E,$0E,$0E,$0E,$0E,$0E DB $0E,$0E,$0E,$0E,$0E,$0E,$0E,$0E,$0E,$0E DB $0E,$0E,$0E,$0E,$0E,$0E,$0E,$0E,$0E,$0E DB $0E,$0E,$0E,$0E,$0E,$0E,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D,$0D DB $0D,$0D,$0D,$0D
and ventana.z80
DB $16,$17,$17,$17,$17,$17,$17,$17,$17,$17 DB $17,$17,$17,$17,$17,$17,$17,$17,$17,$18 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$1D,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$19,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$1D,$15,$16,$17,$17,$17 DB $17,$17,$17,$17,$17,$17,$17,$17,$17,$17 DB $17,$18,$15,$19,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$1D,$15,$1D,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$19,$15,$19,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$1D,$15 DB $1D,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$19,$15,$19,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $1D,$15,$1D,$15,$15,$15,$1E,$1F,$20,$21 DB $22,$20,$22,$15,$15,$15,$15,$19,$15,$19 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$1D,$15,$1D,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$19 DB $15,$19,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$1D,$15,$1D,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$19,$15,$19,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$1D,$15,$1D,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$19,$15,$19,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$1D,$15 DB $1D,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$19,$15,$19,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $1D,$15,$1D,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$19,$15,$19 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$1D,$15,$1D,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$19 DB $15,$19,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$1D,$15,$1D,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$19,$15,$19,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$1D,$15,$1D,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$19,$15,$19,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$1D,$15 DB $1D,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$19,$15,$19,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $1D,$15,$1C,$1B,$1B,$1B,$1B,$1B,$1B,$1B DB $1B,$1B,$1B,$1B,$1B,$1B,$1B,$1A,$15,$19 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$1D,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$19,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$1C,$1B,$1B,$1B,$1B,$1B DB $1B,$1B,$1B,$1B,$1B,$1B,$1B,$1B,$1B,$1B DB $1B,$1B,$1B,$1A,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15,$15,$15,$15,$15,$15,$15 DB $15,$15,$15,$15
The Result:
To generate graphs of this example. I used the GBTD (Gameboy Tile Designer) and GBMB (Gameboy Map Builder), two great tools that allow us to design tiles and maps visually and then generate the data ready for import into our asm programs RGBDS, C GBDK, etc. Wine also function perfectly, for if you want to use from Gnu/Linux (I myself use so). You can download them from:
http://www.devrs.com/gb/hmgd/gbtd.html
http://www.devrs.com/gb/hmgd/gbmb.html
The GBTD, is very simple to use, selecting tiles going right, and going by drawing on the grid, with some typical tools such as brush, bucket fill, etc, and some not so typical as displacements, etc. Also you can select more grnades modes (eg 16 × 16), who then put it back in 8 × 8 mode, you will see how the tiles are aligned in memory for use. Once your game designed tiles, which grabais, and you give it to "Export To" in the menu "File". There is simple, poneis a filename, type "RGBDS Assembly File", a label name (Label), and a section, and From and To poneis ranges sprites to export, if ye have drawn 10 sprites, poneis as 0 through 9, and activais the checkbox "Export Tiles as one unit". You give it to export, and you have your file .Z80 with Data Pointsa of tiles. I usually clean the entire file and just let the DB with the data, since the ground cover in my own labels and others, and sometimes the definitions of banks and extras that add GBTD do nothing but hinder.
Then GBMB, it is also very easy to use. The first to open is going to "Map Properties" from the "File" menu and select there the height and width of the map, for these examples, I always use 32 × 32. The issue is that although it could create a map of eg 20 × 18 for the visible part of the screen when loading it into memory, would have to go by the "jumps", I mean, have to complete 20 bytes memory map, then skip 12 (for 32 complete fill another 20, skip 12, etc. This is not difficult, but when the demos do not need to save memory, and so can use subroutines that have already defined. Then you need to select a file from tiles created with GBTD. You give it to Browse and seleccionais which you have created. You give to "OK" and now you will have your editable map and on the right the list of tiles that you have created. Nothing It is now a matter of going creating the map, filling it with tiles that you have available. Once tengais map ready, go to "Export To" menu "File". As before, seleccionais one nommbre file, type "RGBDS Assembly File ", a tag name, a section (eg 0) and then going to the second tab of" Location Format ". We define as there is stored in the map memory. First on the big picture, the first property seleccionais (out with 1), and seleccionais "[Tile Number], and poneis of 8 bits. Then right, seleccionais, "Map Layout", "Rows", "Plane count": "1 plane, 8 bits" and the rest as is, "Tiles are continues" and Offset 0. You give and you have to export and data ready. And useful file is best to clean and leave only the DB's relevant.
For this example, we will use the internal timer of the GameBoy, that will help us track time accurately. Besides making use of interruptions and not have to worry about bringing us over, we'll let each time a timer interrupt occurs, a subroutine worry to control time.
The timer has 3 records. rTAC, rTMA and rTIMA.
rTAC is the timer control register allows us to switch it off, and adjust the operating frequency. Bit 2 of this register enables or disables the timer (1 = enabled) and bits 0 and 1, allow you to adjust the frequency according to the following table:
00: 4096 Hz (~4194 Hz SGB) 01: 262144 Hz (~268400 Hz SGB) 10: 65536 Hz (~67110 Hz SGB) 11: 16384 Hz (~16780 Hz SGB)
We will use the frequency of 4096Hz, which have the timer is activated every 1/4096 = 0.0002414 seconds.
Each time the timer is activated, increases rTIMA content registration unit. rTIMA is an 8-bit register, so you can count from 0 to 255. When the registration rTIMA overflows (goes from 255 to 0 again), a timer interrupt is generated, if enabled, the CPU will pass control the program to the subroutine timer interrupt handling in the $0050 address.
The third record is rTMA, and is the initial value that is loaded into rTIMA after an overflow, so when rTIMA reaches 255, the overflow generates the interrupt, and then start counting again from the value contained in rTMA, This allows us to adjust how often the most easily interrupt is generated.
So, for this program, a stopwatch, ideally 1 to have in one second. So if we have the frequency is 4096Hz, we know rTIMA increases every 1/4096 = 0.0002414 seconds. Now if we start value rTIMA 51, we have, (1/4096) * (255-51) = (1/4096) * 204 = 0.049 s. Then we know that an interrupt is generated each about 0.05 seconds. If now in the interrupt routine, you do count the times I called, and wait until the call 20 times, we have 0.05 * 20 = 1 second.
In addition to this program, we will use other interruption, disruption of VBlank, which is generated whenever the LCD into the vertical interval period. We call from there, to the drawing routine, always we are sure that the drawing will occur while we are in VBlank :-)
I leave you with the code, you can start or stop the stopwatch with the A button, and reset to zero with B:
; Hello Timer ; David Pello 2010 ; ladecadence.net ; For the tutorial(Spanish): ; http://wiki.ladecadence.net/doku.php?id=tutorial_de_ensamblador INCLUDE "gbhw.inc" ; import file definitions ; Variables _CONTROL_TIEMPO EQU _RAM ;controls milliseconds _ACTIVADO EQU _RAM+1 ; Stopwatch activated or not _SEGUNDOS EQU _RAM+2 _MINUTOS EQU _RAM+3 _HORAS EQU _RAM+4 _PAD EQU _RAM+5 ; Constants _POS_CRONOM EQU _SCRN0+32*4+6 ; screen position ; VBlank interruption SECTION "Vblank",HOME[$0040] call DibujaCronometro reti ; timer overflow interrupt SECTION "Timer_Overflow",HOME[$0050] ; when there is an interruption of the timer, we call this subroutine call ControlTimer reti ; The program begins here: SECTION "start",HOME[$0100] nop jp inicio ; Head of the ROM (Macro defined in gbhw.inc) ; defines a rom without mapper, without 32K RAM, the basics ; (Such as Tetris) ROM_HEADER ROM_NOMBC, ROM_SIZE_32KBYTE, RAM_SIZE_0KBYTE ; here begins our program: inicio: nop di ; disables interrupts ld sp, $ffff ; We aim pile atop the ram initialization: inicializacion: ; We started the timer ld a, 0 ld [rTAC], a ; off timer, divider to 00 (4096 Hz) ld a, 51 ld [rTMA], a ; when TIMA overflows, this is ; reset value, (1/4096) * (255-51) = 0,049 s ld [rTIMA], a ; initial value of the timer. ; begin the variables ld a, 0 ld [_CONTROL_TIEMPO], a ld [_ACTIVADO], a ld [_SEGUNDOS], a ld [_MINUTOS], a ld [_HORAS], a ; pallet ld a, %11100100 ; Palette colors from the darkest to ; Lighter, 11 10 01 00 ld [rBGP], a ; We write this in the background palette register ld [rOBP0], a ; and sprite palette 0 ; scroll ld a, 0 ; We write 0 to scroll records X and Y ld [rSCX], a ; whereby the visible screen positioned ld [rSCY], a ; at the beginning (upper left) of the fund. ; video call apaga_LCD ; We call the routine that turns off the LCD ; the tiles loaded in memory ld hl, Tiles ; HL loaded in the direction of our tile ld de, _VRAM ; address in the video memory ld bc, FinTiles-Tiles ; number of bytes to copy call CopiaMemoria ; We clean the map ld de, _SCRN0 ; map 0 ld bc, 32*32 ld l, 11 ; empty tile call RellenaMemoria ; well, we all loaded map tiles ; sprite attribute memory ld de, _OAMRAM ; sprite attribute memory ld bc, 40*4 ; 40 sprites x 4 bytes each ld l, 0 ; we will start fresh, so the sprites call RellenaMemoria ; Unused remain off-screen ; We draw the stopwatch call DibujaCronometro ; configure and activate the display ld a, LCDCF_ON|LCDCF_BG8000|LCDCF_BG9800|LCDCF_BGON|LCDCF_OBJ8 ld [rLCDC], a ; main control loop: control: ; We read the pad call lee_pad ; Now activate or deactivate the timer ld a, [_PAD] and %00000001 ; Button A call nz, Activa ; Reset ld a, [_PAD] and %00000010 ; Button B call nz, Resetea ld bc, 15000 call retardo ; we start jr control ; Timer control routine Activa: ld a, [_ACTIVADO] cp 1 jp z, .desactiva ; If enabled, disable it ; if not, activate ld a, 1 ld [_ACTIVADO], a ld a, %00000100 ; timer activated ld [rTAC], a ld a, %00000101 ; VBlank timer interrupts and ld [rIE], a ei ; activate the interruptions ret ; return .desactiva ld a, 0 ld [_ACTIVADO], a ld a, %00000000 ; timer disabled ld [rTAC], a ld a, %00000101 ; VBlank timer interrupts and ld [rIE], a di ; deactivate the interruptions ret ; resets the timer Resetea: ld a, 0 ld [_SEGUNDOS], a ld [_MINUTOS], a ld [_HORAS], a ld a, 51 ; initial value of the timer ld [rTIMA], a ; look if activated ld a, [_ACTIVADO] ret z ; if not, we redraw call EsperaVBlank call DibujaCronometro ret DibujaCronometro: ; dozens of hours ld a, [_HORAS] and $F0 swap a ld [_POS_CRONOM], a ; hours ld a, [_HORAS] and $0F ld [_POS_CRONOM+1], a ; : ld a, 10 ld [_POS_CRONOM+2], a ; tens of minutes ld a, [_MINUTOS] and $F0 swap a ld [_POS_CRONOM+3], a ; minutes ld a, [_MINUTOS] and $0F ld [_POS_CRONOM+4], a ; : ld a, 10 ld [_POS_CRONOM+5], a ; tens of seconds ld a, [_SEGUNDOS] and $F0 swap a ld [_POS_CRONOM+6], a ; seconds ld a, [_SEGUNDOS] and $0F ld [_POS_CRONOM+7], a ret ; Controls the time ControlTimer: ld a, [_CONTROL_TIEMPO] cp 20 ; interruptions every 20 passes 1 sec jr z, .incrementa inc a ; if not, we increased and become ld [_CONTROL_TIEMPO], a ret .incrementa ; we reset the counter ld a, 0 ld [_CONTROL_TIEMPO], a ; we increased the latter ld a, [_SEGUNDOS] inc a daa cp 96 ; 60 seconds have passed? (96 because we use BCD) jr z, .minutos ; if you control the minutes ld [_SEGUNDOS], a ; no, we keep and return ret .minutos ld a, 0 ld [_SEGUNDOS], a ; minute, seconds increase to 0 ld a, [_MINUTOS] inc a daa cp 96 ; 60 minutes have passed? jr z, .horas ; if you control the hours ld [_MINUTOS], a ; no, we keep and return ret .horas ld a, 0 ld [_MINUTOS], a ; minute, seconds increase to 0 ld a, [_HORAS] inc a daa cp 36 ; 24 hours have passed? (36 equals 24 BCD) jr z, .reset ; if you start to re- ld [_HORAS], a ; no, we keep and return ret .reset call Resetea ret ; Routine reading pad lee_pad: ; a zero ld a, 0 ld [_PAD], a ; we will read the crosshead: ld a, %00100000 ; bit 4-0, 5-1 bit (on crosshead, buttons not) ld [rP1], a ; now see the status of the cross, to avoid bouncing ; We do several readings ld a, [rP1] ld a, [rP1] ld a, [rP1] ld a, [rP1] and $0F ; only care about the bottom 4 bits. swap a ; lower and upper exchanged. ld b, a ; We keep the state of the cross in b ; we go for the buttons ld a, %00010000 ; bit 4 to 1, bit 5 to 0 (enabled buttons, not Cross) ld [rP1], a ; We read several times to avoid bouncing ld a, [rP1] ld a, [rP1] ld a, [rP1] ld a, [rP1] ld a, [rP1] ld a, [rP1] ; we at A, the state of the buttons and $0F ; only care about the bottom 4 bits. or b ; or make a to b, to "meter" in Part ; A top of the crosshead state. ; we now have at A, the state of all, we complement and ; store it in the variable cpl ld [_PAD], a ; we reset the pad ld a,$30 ld [rP1], a ; return ret ; LCD shutdown routine apaga_LCD: ld a,[rLCDC] rlca ;It sets the high bit of LCDC in the carry flag ret nc ; Display is already off, again. ; We VBlank hope to, because we can not turn off the screen ; some other time call EsperaVBlank ; we are in VBlank, we turn off the LCD ld a,[rLCDC] ; in A, the contents of the LCDC res 7,a ; we zero bit 7 (on the LCD) ld [rLCDC],a ; We wrote in the LCDC register content A ret ; return EsperaVBlank: ld a, [rLY] cp 145 jr nz, EsperaVBlank ret ; delay routine ; parameters ; bc - number of iterations retardo: .delay: dec bc ; decrement ld a, b ; see if zero or c jr z, .fin_delay nop jr .delay .fin_delay: ret ; memory copy routine ; copy a number of bytes from one direction to another ; expects the parameters: ; hl - copying data address ; of - destination address ; bc - amount of data to be copied ; destroys the contents of A CopiaMemoria: ld a, [hl] ; To load the data in A ld [de], a ; copy the data to the destination dec bc ; least one copy ; We check if bc is zero ld a, c or b ret z ; If zero, we return ; if not, we still inc hl inc de jr CopiaMemoria ; Fill routine memory ; filled a number of bytes of memory with a data ; expects the parameters: ; of - destination address ; bc - amount of data to fill ; l - data to fill RellenaMemoria: ld a, l ld [de], a ; puts the data in the destination dec bc ; least one fill ld a, c or b ; We check if bc is zero ret z ; If zero return inc de ; if not, we still jr RellenaMemoria Tiles: ; numbers 0 through 9 format tiles of RGBDS ; 0 DW `00000000 DW `00333300 DW `03000330 DW `03003030 DW `03030030 DW `03300030 DW `00333300 DW `00000000 ; 1 DW `00000000 DW `00003000 DW `00033000 DW `00003000 DW `00003000 DW `00003000 DW `00333300 DW `00000000 ; 2 DW `00000000 DW `00333300 DW `03000030 DW `00003300 DW `00030000 DW `00300000 DW `03333330 DW `00000000 ; 3 DW `00000000 DW `00333300 DW `03000030 DW `00003300 DW `00000030 DW `03000030 DW `00333300 DW `00000000 ; 4 DW `00000000 DW `00000300 DW `00003300 DW `00030300 DW `00333300 DW `00000300 DW `00000300 DW `00000000 ; 5 DW `00000000 DW `03333330 DW `03000000 DW `00333300 DW `00000030 DW `03000030 DW `00333300 DW `00000000 ; 6 DW `00000000 DW `00003000 DW `00030000 DW `00300000 DW `03333300 DW `03000030 DW `00333300 DW `00000000 ; 7 DW `00000000 DW `03333330 DW `00000300 DW `00003000 DW `00030000 DW `00300000 DW `00300000 DW `00000000 ; 8 DW `00000000 DW `00333300 DW `03000030 DW `00333300 DW `03000030 DW `03000030 DW `00333300 DW `00000000 ; 9 DW `00000000 DW `00333300 DW `03000030 DW `03000030 DW `00333300 DW `00003000 DW `00330000 DW `00000000 ; : DW `00000000 DW `00033000 DW `00033000 DW `00000000 DW `00033000 DW `00033000 DW `00000000 DW `00000000 ; empty tile DW `00000000 DW `00000000 DW `00000000 DW `00000000 DW `00000000 DW `00000000 DW `00000000 DW `00000000 FinTiles:
ROM: hola-timer.gb
Result:
I've been told it would be nice to explain a couple of functions, so let's do this:
First I will explain ControlTimer, because it will help us to better explain the other.
; Controls the time ControlTimer: ld a, [_CONTROL_TIEMPO] cp 20 ; interruptions every 20 passes 1 sec jr z, .incrementa inc a ; if not, we increased and become ld [_CONTROL_TIEMPO], a ret
As explained above, we have the timer to 4096Hz and the timer initial value 51, calls the interrupt timer each approximately .05 seconds, so to count from 1 to 1 seconds to the timer, what we do here is count the times that calls this subroutine, if it has been called 20 times, it's been 0.05 * 20 = 1 second, so let's change the time, if not, increment the counter and return.
.incrementa ; we reset the counter ld a, 0 ld [_CONTROL_TIEMPO], a ; we increased the latter ld a, [_SEGUNDOS] inc a daa cp 96 ; 60 seconds have passed? (96 because we use BCD) jr z, .minutos ; if you control the minutes ld [_SEGUNDOS], a ; no, we keep and return ret
Well, now we will increase the time that has already passed one second, first we reset the counter of times that calls the function, because they have as 20th, we need to recount from zero.
Then we will increase the latter. A charge in the second, and we add one, then we have to see if the result is greater than 60, because if so, we have to set seconds to zero, and adding a minute. But you see daa use. DAA is an instruction that it does is convert the contents of A BCD (Binary Coded Decimal), if you know BCD, binary numbers instead of stored as is, stored in a byte so that each 4 bits represent a number from 0 to 9, and not from 0 to 15 as base 2 could be ordinary. This is very useful because although one byte can only store of 0-99 in each nibble I have tens and units respectively in the number of seconds / minutes / hours, and this will be very useful and then draw them. So I turn the content of A BCD and compare it to 60 (in BCD is 0110 (6) and 0000 (0), 01.1 million and that is 96 represented normal binary, which is what means the processor to compare that binary content is equal, so I compare with 96), if we have more than 60 seconds, jump to increase the minutes, if not, I keep the result in the second, and again.
.minutos ld a, 0 ld [_SEGUNDOS], a ; ; minute, seconds increase to 0 ld a, [_MINUTOS] inc a daa cp 96 ; 60 minutes have passed? jr z, .horas ; if you control the hours ld [_MINUTOS], a ; no, we keep and return ret .horas ld a, 0 ld [_MINUTOS], a ; minute, seconds increase to 0 ld a, [_HORAS] inc a daa cp 36 ; 24 hours have passed? (36 corresponds to 24 in BCD) jr z, .reset ; if you start to re- ld [_HORAS], a ; no, we keep and return ret .reset call Resetea ret
And here it is almost the same for the minutes and hours. For minutes, the first thing I do is set seconds to zero (59-> 0) and then increase the minutes and do the same thing with the second, check if it has been 60 and afrimativo case go to increase the hours, and if not return. And with the same hours, only if they spend 24 hours, reset, I put everything to zero, and ale, to start over.
And now explain the routine that draws the clock, which is very simple. At the beginning I defined a constant named _POS_CRONOM, such that _SCRN0 + 32 * 4 + 6. What does that mean?_SCRN0 Was the address in memory of the background map, where it is kept that tile paint at each position 32 * 32 tiles background. If I add that 32 * 4, get 32 bytes later * 4 = fourth line, and if most 6 because the fourth line of the screen, the 6-position, the point where start to draw the stopwatch. And from here it is very simple. I have to start drawing on the tens of hours, then hours, then the two points, then tens of minutes ... etc. For seeing the beginning of the routine:
; dozens of hours ld a, [_HORAS] and $F0 swap a ld [_POS_CRONOM], a ; hours ld a, [_HORAS] and $0F ld [_POS_CRONOM+1], a ; : ld a, 10 ld [_POS_CRONOM+2], a
Position on the record, the contents of variable hours, as it is stored in BCD, I know I have the tens of hours and lower units in the upper nibble. So to draw tens, I do a and of its contents with $ F0, which is% 11110000, so this clears the contents of the middle bottom, leaving just the middle top, and then doing swap, change the middle part the average high floor, so now I have in A, only the number of tens of hours.Then simply charge _POS_CRONOM that number in the position that I shall bring that number at that location map screen. We know that the gameboy, drawn background picking the tiles from the list according to the number of tiles that say you, then get into that position of the tile background equal to the number of tens of hours. As our tiles 0 through 9 of the drawings are precisely the numbers 0 through 9, the gameboy drawn in that position number corresponding to the tens of hours. Then for units of hours, do something similar, but what we do is the opposite and, with $ 0F, because we only want to stay with the bottom (units) and then we need the swap and draw directly to the corresponding tile number, but now en_POS_CRONOM + 1, because we want to draw a position further to the right. Then we draw the colon (tile 10), and do the same for tens of minutes, units of minutes, etc., simply by adding positions to the initial direction to draw the remaining numbers.
And even here this example. Next, banks.
As had been mentioned, the Gameboy only can directly handle a 64K address space, and they have to handle RAM, video memory, registers input / output, etc., so that the cartridge data, we have available only 32K, organized into two 16K blocks, one of $ 0000 to $ 3FFF and another $ 4000 to $ 7FFF.
Then, when we need more memory for our programs, we have to use the mappers known. A mapper is a chip that resides in the cartridge, and listening certain "commands" that you can send to let us dispobible more memory cartridges at our request. This is achieved by banking. Let's assume that the first block of 16K the $ 0000 to $ 3FFF, is a fixed block that is always available at the beginning of the cartridge memory in that direction. But in the second block, from $ 4000 to $ 7FFF, we can map other areas of the memory cartridge, called banks. In the GameBoy, these banks are always 16K. So for example: If we have a cartridge with a 64K memory, we divide it into 4 blocks of 16K, and we, the block 0 of $ 0000 to $ 3FFF in the cartridge memory, always available in the memory of the gameboy $ 0000 to $ 3FFF, but the other 3 blocks, 1, 2 and 3, we can exchange them in the memory range of the gameboy $ 4000 to $ 7FFF, thus being able to access on that block to the top three banks cartridge memory, one by one when we need them.
To do so, as I said, we need a mapper. During the existence of the Gameboy, varioas of these chips were developed by growing needs. The best known are the MBC1 mappers, the MBC2, the MBC3 and MBC5 appeared as the GameBoy Color. The good thing is that most of these mappers are very similar to each other, only that increasingly allow more memory, but the basic access is often supported. Besides these mappers, they would introduce a very important feature. The allow the use of external RAM in the cartridge, normally protected by a battery so that it retains its contents when you turn off the console, with what we have saved games or important data.
Then we go with the example. The first thing you'll notice is that I changed the head of the cartridge, saying that we now have a cartridge with MBC1 mapper, external RAM and battery cartridge has 64K of ROM and 8K of RAM. We need to define the header correctly in these areas, although the actual GameBoy ignores them and allows us to do things, emulators usually ignore him and just let you use and saving banks if these data are correct.
Meto all possible code zero bank, to start my program with the initial address as always, the linker will place me everything in bank 0 unless told otherwise.
Then at the end of the program, you can see that two banks and define policies and define them. As I told you the top banks to 0, always tendrñan to be mapped in the second block of addresses $ 4000 to $ 7FFF, so I put the start address, but also tell the RGBDS in reality I put that data in X bank in the rom generated. So in ROM cartridge, really bank 0 will be of $ 0000 to $ 3FFF, bank 1 of $ 4000 to $ 7FFF, the bank 2 $ 8000 to BFFF $, etc, but when using the mapper to access those banks, the "will place" in memory of the gameboy $ 4000 to $ 7FFF.
So the program starts and as usual, the default when you start the console, the mapper placed bank 1 then 0, so if we print the message contained in $ 4000, says that is in the bank 1. Now pressing A or B, we can send the commands that tell the mapper change the bank, so now if we print the message in $ 4000, we can be content start printing bank 1 or 2, so that the message changes. Ademñas, if you click Select, we keep the number of the current bank in the external memory, so when encendais the GameBoy (or emulator) again, you will see that "remembers" the last bank number you had selected when you gave to "record "with Select.
Select a bank with MBC1 is simple, simply write the number of the bank you want to select, in the memory address $ 2000. As is nonsense writing in that direction, because in theory are writing in ROM cartridge, which is impossible because it is read-only, the mapper listens to this write attempt and select a bank or another depending on the number you try to write. Very simple.
Then for memory, there is a difference, and we have to activate it. This is done to avoid unnecessary memory accesses, power failures or possible (batteries) that could cause data loss, so when we want to access external memory, activate, read or write, and deactivate. This is accomplished by "writing" A $ 0A at $ 0000 to activate the external memory, and $ 00 at $ 0000 to clear it, and then simply read or write data from $ A000 to $ BFFF. We also have banks of RAM if we need more, and work just like ROM banks but in blocks of 8K, using $ 4000 to select (there writing the bank number we want).
Also I added a little something to this example, as routines to print text strings, or a complete source ASCII characters from 32 (space) to 127. I have done very simple, use the second memory block tiles from $ 8800 to $ 97FF which we know uses numbers from -128 to 127, so zero is $ 9,000. If colo space (ascii 32), 32 tiles above (each tile is 2 bytes per line and 8 lines, 32 * 2 * 8), so at $ 9200, I use normal text in my data to write in the background, as the number of ascii character, match the tile number to draw.What you see on the labels with text I use, I write the text as quoted. The RGBDS will convert these characters to their ASCII values to get them as if byte DB tuck hand. The strings just to 0 (using the comma to add a byte more), so that the printing routines, know where you finish printing.
To generate the source, I created a little program in java, which takes an image of 768 × 8 pixels (96 tiles 8 × 8) grayscale, and we generate data useful in RGBDS format. I will publish soon.
I leave the code example:
; Hello Banks ; David Pello 2010 ; ladecadence.net ; For the tutorial(Spanish): ; http://wiki.ladecadence.net/doku.php?id=tutorial_de_ensamblador INCLUDE "gbhw.inc" ; import file definitions ; Constantes _TILES_FUENTE EQU $9200 ; the second table is useful to $8800 ; to $97FF and the tiles are numbered ; from -128 to 127, with tile 0 ; it is $9000. If I add 32 tiles ; (32 * 2 bytes * 8lineas) and placed there ; the first character (space), I ; match the ASCII code ; and I can use text strings as is ; Variables _BANCO EQU _RAM ; We will keep the current bank _PAD EQU _RAM+1 ; State pad ; interruption VBlank SECTION "Vblank",HOME[$0040] reti ; we do nothing, we return ; The program begins here: SECTION "start",HOME[$0100] nop jp inicio ; Head of the ROM (Macro defined in gbhw.inc) ; defines a MBC1 rom mapper, and 64K RAM ROM_HEADER ROM_MBC1_RAM_BAT, ROM_SIZE_64KBYTE, RAM_SIZE_8KBYTE ; Here begins our program inicio: nop di ; disables interrupts ld sp, $ffff ; We aim pile atop the ram inicializacion: ; We began variables ld a, 1 ld [_BANCO], a ; paletas ld a, %11100100 ; Palette colors from the darkest to ; lighter, 11 10 01 00 ld [rBGP], a ; We write this in the background palette register ld [rOBP0], a ; and sprite palette 0 ; scroll ld a, 0 ; write 0 records scroll in X and Y ld [rSCX], a ; positioned so that the visible screen ld [rSCY], a ; at the beginning (upper left) of the fund. ; video call apaga_LCD ; We call the routine that turns off the LCD ; We load the tiles in the second table memory tiles ld hl, Fuente1 ; HL loaded in the direction of our tile ld de, _TILES_FUENTE ; in direction of the table ld bc, EndFuente1-Fuente1 ; number of bytes to copy call CopiaMemoria ; We clean the map ld de, _SCRN0 ; map 0 ld bc, 32*32 ld l, 0 ; tile vacuum (space) call RellenaMemoria ; well, we all loaded map tiles ; We clean the memory of sprites ld de, _OAMRAM ; sprite attribute memory ld bc, 40*4 ; 40 sprites x 4 bytes each ld l, 0 ; we will start fresh, so the sprites call RellenaMemoria ; Unused fall outside screen ld b, 1 ld c, 1 ld hl, $4000 call ImprimeCadena ld b, 1 ld c, 16 ld hl, Info call ImprimeCadena ; we will read the data stored ld a, $0A ld [$0000], a ; activate external RAM ld a, [$A000] ; We carry in to the data ld b, a ; we keep ld a, $00 ld [$0000], a ; deactivate the external RAM ; we print ld l, b ld b, 18 ld c, 16 call ImprimeNumero ; configure and activate the display ld a, LCDCF_ON|LCDCF_BG8800|LCDCF_BG9800|LCDCF_BGON|LCDCF_OBJ8 ld [rLCDC], a ; main loop control: ; We read the pad call lee_pad ; Now we switch to bank 1 ld a, [_PAD] and %00000001 ; A button call nz, Banco1 ; Or at Bank 2 ld a, [_PAD] and %00000010 ; B button call nz, Banco2 ; Stored in the external RAM ld a, [_PAD] and %000000100 ; Select button call nz, Guarda ld bc, 15000 call Retardo ; we start jr control ; Switch to bank 1 and displays Banco1: ; the bank wrote in the direction we want Bank Select ld hl, $2000 ld [hl], 01 ; Bank 1 ; store it in the variable ld a, 01 ld [_BANCO], a ; We show the message that lies at the beginning of the upper bank call EsperaVBlank ld b, 1 ld c, 1 ld hl, $4000 call ImprimeCadena ; we have changed, delete the message saved if any ld b, 1 ld c, 12 ld hl, Limpia call ImprimeCadena ret ; Switch to bank 2 and displays Banco2: ; the same but select bank 2 ld hl, $2000 ld [hl], 02 ld a, 02 ld [_BANCO], a call EsperaVBlank ld b, 1 ld c, 1 ld hl, $4000 call ImprimeCadena ; we have changed, delete the message saved if any ld b, 1 ld c, 12 ld hl, Limpia call ImprimeCadena ret ; Save the number of actual bank in the cartridge memory Guarda: ; first we activate the external SRAM ld a, $0A ; $0A, activate ld [$0000], a ; ; Write the data in the first byte of the external ram ld a, [_BANCO] ld [$A000], a ; We deactivate the SRAM ld a, $00 ; $00 off ld [$0000], a ; ; we print ld a, [_BANCO] ld l, a ld b, 18 ld c, 16 call ImprimeNumero ; show message ld b, 1 ld c, 12 ld hl, Guardado call ImprimeCadena ret ; Prints a string on the bottom (string ending in 0) ; Parameters: ; b - x coordinate ; c - y coordinate ; hl - chain management ImprimeCadena: push hl ; then keep hl pa ; we will use hl now for destination calculations ld hl, _SCRN0 ; we will position and ld a, c cp 0 jr z, .fin_y ; If zero, we go for the x .avz_y: ld de, 32 add hl, de ; We move on 32 And therefore useful dec a jr nz, .avz_y .fin_y: ; we go for the x ld a, b cp 0 jr z, .fin_x ; If zero, finished .avz_x: inc hl dec a jr nz, .avz_x .fin_x: push hl pop de ; of = hl ; But we 'of' the memory location where the string writing ; we will do pop hl ; hl rescued stack .imprime: ld a, [hl] ; We load a character cp 0 ret z ; If zero, return ld [de], a ; if not, we print inc de ; following inc hl jr .imprime ret ; Prints a number (unit) ; Parameters ; b - position x ; c - position y ; l - number to be printed (0-9) ImprimeNumero: push hl ; then keep hl pa ; we will use hl now for destination calculations ld hl, _SCRN0 ; we will position and ld a, c cp 0 jr z, .fin_y ; If zero, we go for the x .avz_y: ld de, 32 add hl, de ; We move on 32 And therefore useful dec a jr nz, .avz_y .fin_y: ; we go for the x ld a, b cp 0 jr z, .fin_x ; If zero, finished .avz_x: inc hl dec a jr nz, .avz_x .fin_x: push hl pop de ; of = hl ; But we 'of' the memory in which to write the number ; we will do pop hl ; the number rescued ld a, l and $0F ; we are only interested lower add a, 48 ; the first space character is 32, zero is +16 ld [de], a ret ; Routine reading pad lee_pad: ; a zero ld a, 0 ld [_PAD], a ; we will read the cross: ld a, %00100000 ; bit 4-0, 5-1 bit (on Cross, no buttons) ld [rP1], a ; now we read the status of the Cross, to avoid bouncing ; We do several readings ld a, [rP1] ld a, [rP1] ld a, [rP1] ld a, [rP1] and $0F ; only care about the bottom 4 bits. swap a ; lower and upper exchange. ld b, a ; We keep cross status in b ; we go for the buttons ld a, %00010000 ; bit 4 to 1, bit 5 to 0 (enabled buttons, not Cross) ld [rP1], a ; We read several times to avoid bouncing ld a, [rP1] ld a, [rP1] ld a, [rP1] ld a, [rP1] ld a, [rP1] ld a, [rP1] ; we at A, the state of the buttons and $0F ; only care about the bottom 4 bits. or b ; or make a to b, to "meter" in Part ; A superior, cross status. ; we now have at A, the state of all, we complement and ; store it in the variable cpl ld [_PAD], a ; we reset the pad ld a,$30 ld [rP1], a ; return ret ; LCD shutdown routine apaga_LCD: ld a,[rLCDC] rlca ; It sets the high bit of LCDC in the carry flag ret nc ; Display is already off, again. ;We VBlank hope to, because we can not turn off the screen ; some other time call EsperaVBlank ; we are in VBlank, we turn off the LCD ld a,[rLCDC] ; in A, the contents of the LCDC res 7,a ; we zero bit 7 (on the LCD) ld [rLCDC],a ; We wrote in the LCDC register content A ret ; return EsperaVBlank: ld a, [rLY] cp 145 jr nz, EsperaVBlank ret ; delay routine ; parameters ; bc - number of iterations Retardo: .delay: dec bc ; decrement ld a, b ; see if zero or c jr z, .fin_delay nop jr .delay .fin_delay: ret ; memory copy routine ; copy a number of bytes from one direction to another ;expects the parameters: ;hl - copying data address ;of - destination address ; bc - number of data to be copied ; destroys the contents of A CopiaMemoria: ld a, [hl] ; To load the data in ld [de], a ; copy the data to the destination dec bc ; least one copy ; We check if bc is zero ld a, c or b ret z ; If zero, we return ; if not, we still inc hl inc de jr CopiaMemoria ; Fill routine memory ; filled a number of bytes of memory with a data ; expects the parameters: ;of - destination address ; bc - amount of data to fill ; l - data to fill RellenaMemoria: ld a, l ld [de], a ; puts the data in the destination dec bc ; least one fill ld a, c or b ; We check if bc is zero ret z ; If zero return inc de ; if not, we still jr RellenaMemoria Limpia: DB " ",0 Info: DB "Banco guardado: ", 0 Guardado: DB "Guardado correcto", 0 ; Font ; ======================================================================== INCLUDE "fuente1.agb" ; First Data Bank ; ======================================================================== SECTION "Banco1",CODE[$4000],BANK[1] DB "Soy el banco 1",0 ; The second Banco data ; ======================================================================== SECTION "Banco2",CODE[$4000],BANK[2] DB "Soy el banco 2",0
And the source file: fuente1.agb
La captura obligada:
The sound of the GameBoy is a complex issue if you come to schedule a modern system where you play digital sound simply, easily mixing music and effects. The GameBoy by contrast, has a hardware sound quite simple, consisting of four separate and distinct channels each. We can control these channels at will, which is responsible for the GameBoy mix. These four channels are handled by typing values in registers mapped into memory (of course), and each channel has multiple records for each of its parameters, so the subject takes a while to explain. Let us know and then we'll see channels as scheduled.
Well, for starters, I'll explain a little music theory, well, just what interests us, the duty cycle, the envelope and portamento.
The duty cycle is very simple, if we need wave is square, is just how much of the time duration of the note, we have the highest level, and how long the low level. This is expressed in percent. So in the GamebBoy we can choose working cycles of 12.5%, 25%, 50% and 75%. Let's explain this with an image stolen from out there:
You see, the waves have the same frequency (all start at the same time), but depending on the duty cycle, some will spend more time and more time up below.
The envelope of a note is a function that applies to the extent of this, to modify and to vary along the length thereof.In short, you typically use an envelope called ADSR, of Attack , Decay , Sustain and Release , ie, attack, decay, sustain and relaxation. We will put a graphic ... Wikipedia to the rescue. If we have a normal note, would have a certain amplitude (volume) fixed, the graph of its size, would be a straight line. Now if we apply an ADSR envelope model, we would have something like this:
With so watching this, we can say that the attack is the time it takes for the note to reach its maximum amplitude, decay, is the time it takes to move from the end of the attack, to the value of support, which is the amplitude value we hold until the end of the note, and relaxation, the time it takes the note "disappear" from the just touching. With all these envelope parameters, we can model the notes so they change enough but we are playing the same frequency.
Portamento is when the pitch from one note to another, is not direct, but the frequency is changing gradually playing all intermediate sounds. This for example in a guitar is made when the finger on all frets between two notes creeps want to play. In the GameBoy hardware portamento this allows us a note vary over time and may increase or decrease set a base frequency over time, and the changes this frequency.
Let's see then records the sound system and their functions:
Bit 6-4 - Length of portamento Bit 3 - Increase / Decrease the portamento 0 Addiction (frequency increases) 1: Abduction (the frequency decreases) Bit 2-0 - Step displacement current (n: 0-7)
Duration of portamento:
000: Off - frequency unchanged 001: 7.8 ms (1 / 128Hz) 010: 15.6 ms (2 / 128Hz) 011: 23.4 ms (3 / 128Hz) 100: 31.3 ms (4 / 128Hz) 101: 39.1 ms (5 / 128Hz) 110: 46.9 ms (6 / 128Hz) 111: 54.7 ms (7 / 128Hz)
Changing the original frequency (defined in NR13, NR14) at each step, it is calculated with the following formula, where X (0) is the initial and X (t-1) frequency is the last frequency:
X(t) = X(t-1) +/- X(t-1)/2^n
Bit 7-6 - Duty Cycle (R / W) Bit 5-0 - Length (R) (t1: 0-63)
Duty cycle:
00: 12.5% ( _-------_-------_------- ) 01: 25% ( __------__------__------ ) 10: 50% ( ____----____----____---- ) (normal) 11: 75% ( ______--______--______-- )
Length = (64-t1) * (1/256) second length is used only if Bit 6 in NR14 is one.
Bit 7-4 - Initial volume of the envelope (0-0Fh) (0 = no sound) Bit 3 - Address the envelope (0 = Decreased, 1 = Grows) Bit 2-0 - period (n: 0-7) (If it is zero, the envelope does not act.)
Length of one step = n * (1/64) seconds
Low 8 bits of the 11 bit frequency (x). The following 3 bits are NR14 ($ FF14)
Bit 7 - timer (1 = Restart Sound) (W) Bit 6 - Turns the length (R / W) (1 = stops the sound when it reaches the length in NR11) Bit 2-0 - 3 upper bits of frequency (x) (W)
Frequency = 131072 / (2048-x) Hz
This channel works just like 1, but has no record of portamento.
Bit 7-6 - Duty Cycle (R / W) Bit 5-0 - Length (R) (t1: 0-63)
Duty cycle:
00: 12.5% ( _-------_-------_------- ) 01: 25% ( __------__------__------ ) 10: 50% ( ____----____----____---- ) (normal) 11: 75% ( ______--______--______-- )
Length = (64-t1) * (1/256) second length is used only if Bit 6 in NR14 is one.
Bit 7-4 - Initial volume of the envelope (0-0Fh) (0 = no sound) Bit 3 - Address the envelope (0 = Decreased, 1 = Grows) Bit 2-0 - period (n: 0-7) (If it is zero, the envelope does not act.)
Length of one step = n * (1/64) seconds
Low 8 bits of the 11 bit frequency (x). The following 3 bits are NR24 ($ FF19)
Bit 7 - timer (1 = Restart Sound) (W) Bit 6 - Turns the length (R / W) (1 = stops the sound when it reaches the length in NR21) Bit 2-0 - 3 upper bits of frequency (x) (W)
Frequency = 131072 / (2048-x) Hz
This channel can be used to play digital sound, but the length of the buffer samples (Wave RAM) is limited to 32 values of 4 bits. One could use this channel to take notes if inicicalizamos normal wave memory with the values of a square wave. No envelope control.
Bit 7 - Turns the channel (0 = Stopped, 1 = Plays) (R / W)
Bit 7-0 - Length (t1: 0-255)
Length = (256-t1) * (1/256) seconds. This value is used only if Bit 6 in NR34 is one.
Bit 6-5 - Select output volume (R / W)
Possible values are:
00: Off (no sound) 01: 100% Volume (Data RAM wave as is) 10: 50% Volume (Data RAM wave of displaced once right) 11: 25% Volume (Data RAM wave of displaced twice right)
Low 8 bits of the channel frequency (x).
Bit 7 - timer (1 = Restart Sound) (W) Bit 6 - Turns the length (R / W) (1 = stops the sound when it reaches the length in NR11) Bit 2-0 - 3 upper bits of frequency (x) (W)
Frequency = 131072 / (2048-x) Hz
It contains the values for generating the wave.
This memory is organized into 32 4-bit values that reproduce high 4 bits first.
This channel is used to play white noise. This is accomplished by varying the amplitude randomly given frequency. Depending on the frequency, noise seem more "hard" or "soft."
You can also influence the output of random number generator, thus allowing less variation and output almost normal tone.
Bit 5-0 - Length (R) (t1: 0-63)
Length = (64-t1) * (1/256) second length is used only if Bit 6 in NR44 is one.
Bit 7-4 - Initial volume of the envelope (0-0Fh) (0 = no sound) Bit 3 - Address the envelope (0 = Decreased, 1 = Grows) Bit 2-0 - period (n: 0-7) (If it is zero, the envelope does not act.)
Length of one step = n * (1/64) seconds
The amplitude is randomly changes between high and low at the given frequency. A high frequency will make the "soft" noise. When bit 3 is set, the output is more regular, and some frequencies sound more like tone as noise.
Bit 7-4 - Frequency shift clock (s) Bit 3 - Length counter (0 = 15 bits, 1 = 7 bits) Bit 2-0 - Radio frequency division (r)
Frequency = 524288 Hz / r / 2 ^ (s + 1); For r = 0, it behaves as if r = 0.5
Bit 7 - timer (1 = Restart Sound) (W) Bit 6 - Turns the length (R / W) (1 = stops the sound when it reaches the length in NR41)
Volume bits control the main volume of the left and right outputs.
Bit 7 - Vin out for SO2 (1 = enabled) Bit 6-4 - Volume of SO2 (0-7) Bit 3 - Vin goes by SO1 (1 = On) Bit 2-0 - Volume of SO1 (0-7)
The signal coming from the bus Vin cartridge, allowing external hardware in this, add a fifth channel to the four inmates of the GameBoy.
Volume bits control the main volume of the left and right outputs.
Bit 7 - Channel 4 at the outlet SO2 Bit 6 - Channel 3 at the outlet SO2 Bit 5 - Channel 2 to the output SO2 Bit 4 - Channel 1 to the output SO2 Bit 3 - Channel 4 to the output SO1 Bit 2 - Channel 3 to the output SO1 Bit 1 - Channel 2 to the output SO1 Bit 0 - Channel 1 to the output SO1
If your program does not use sound GameBoy, write 00h in this register, you will save 16% or more of power consumption. Disable the sound system by resetting the bit 7 destroys the contents of all sound recordings. Furthermore, let all these records inaccessible (except the same FF26 $) while the sound system is turned off.
Bit 7 - Turns all sound (0: Stop all sound circuits) (R / W) Bit 3 - Channel 4 ON (R) Bit 2 - Channel 3 ON (R) Bit 1 - Channel 2 ON (R) Bit 0 - Channel 1 ON (R)
Bits 0-3 of this register are only status bits, write them not activate or deactivate the sound of those channels. These flags are set to one when the flag "Trigger" channel (Bit 7 in NR14-NR44), the flag remains one as long as the length of the sound (if enabled) is activated. An envelope that has driven down to zero amplitude not deactivate this flag.
Well, let an example, we rescued the sprite program, and add music, well, if this is you can call music What I do is go playing the notes of the scale (Do, Re, Mi, Fa, Sol, La, Si), 5th eighth time to time. I have a data line with the low values of the frequency of these notes, and the high value, which is always the same notes in the 5th octave fixed stick it in the register. To use this channel 2. Pay attention to where active sound system and prepare the output from both speakers, and prepare the channel 2, length, duty cycle, envelope and put my high frequency value and functions iniciar_sonido active length, and cambia_nota function that checks if we have to play the note (depending on the constant TEMPO) and play the note and prepares NEXT.
; Hola sonido ; David Pello 2010 ; ladecadence.net ; Para el tutorial en: ; http://wiki.ladecadence.net/doku.php?id=tutorial_de_ensamblador INCLUDE "gbhw.inc" ; importamos el archivo de definiciones ; Velocidad de la musica, no es un tempo real en el sentido musical _TEMPO EQU 20 ; definimos unas constantes para trabajar con nuestro sprite _SPR0_Y EQU _OAMRAM ; la Y del sprite 0, es el inicio de la mem de sprites _SPR0_X EQU _OAMRAM+1 _SPR0_NUM EQU _OAMRAM+2 _SPR0_ATT EQU _OAMRAM+3 ; creamos un par de variables para ver hacia donde tenemos que mover el sprite _MOVX EQU _RAM ; inicio de la ram dispobible para datos _MOVY EQU _RAM+1 ; guarda la nota actual _NOTA EQU _RAM+2 ; contador para el tempo _CONT_MUS EQU _RAM+3 ; El programa comienza aqui: SECTION "start",HOME[$0100] nop jp inicio ; Cabecera de la ROM (Macro definido en gbhw.inc) ; define una rom sin mapper, de 32K y sin RAM, lo más básico ; (como por ejemplo la del tetris) ROM_HEADER ROM_NOMBC, ROM_SIZE_32KBYTE, RAM_SIZE_0KBYTE ; aqui empieza nuestro programa inicio: nop di ; deshabilita las interrupciones ld sp, $ffff ; apuntamos la pila al tope de la ram inicializacion: ld a, %11100100 ; Colores de paleta desde el mas oscuro al ; más claro, 11 10 01 00 ld [rBGP], a ; escribimos esto en el registro de paleta de fondo ld [rOBP0], a ; y en la paleta 0 de sprites ld a, 0 ; escribimos 0 en los registros de scroll X e Y ld [rSCX], a ; con lo que posicionamos la pantalla visible ld [rSCY], a ; al inicio (arriba a la izq) del fondo. call inicia_sonido ; iniciamos el sistema de sonido call apaga_LCD ; llamamos a la rutina que apaga el LCD ; cargamos los tiles en la memoria de tiles ld hl, Tiles ; cargamos en HL la dirección de nuestro tile ld de, _VRAM ; en DE dirección de la memoria de video ld b, 32 ; b = 32, numero de bytes a copiar (2 tiles) .bucle_carga: ld a,[hl] ; cargamos en A el dato apuntado por HL ld [de], a ; y lo metemos en la dirección apuntada en DE dec b ; decrementamos b, b=b-1 jr z, .fin_bucle_carga ; si b = 0, terminamos, no queda nada por copiar inc hl ; incrementamos la dirección a leer de inc de ; incrementamos la dirección a escribir en jr .bucle_carga ; seguimos .fin_bucle_carga: ; ahora limpiamos la pantalla (llenamos todo el mapa de fondo), con el tile 0 ld hl, _SCRN0 ld de, 32*32 ; numero de tiles en el mapa de fondo .bucle_limpieza: ld a, 0 ; el tile 0 es nuestro tile vacio ld [hl], a dec de ; ahora tengo que comprobar si de es cero, para ver si tengo que ; terminar de copiar. dec de no modifica ningñun flag, asi que no puedo ; comprobar el flag zero directamente, pero para que de sea cero, d y e ; tienen que ser cero los dos, asi que puedo hacer un or entre ellos, ; y si el resultado es cero, ambos son cero. ld a, d ; cargamos d en a or e ; y hacemos un or con e jp z, .fin_bucle_limpieza ; si d OR e es cero, de es cero. Terminamos. inc hl ; incrementamos la dirección a escribir en jp .bucle_limpieza .fin_bucle_limpieza ; bien, tenemos todo el mapa de tiles lleno con el tile 0, ; ahora vamos a crear el sprite. ld a, 30 ld [_SPR0_Y], a ; posición Y del sprite ld a, 30 ld [_SPR0_X], a ; posición X del sprite ld a, 1 ld [_SPR0_NUM], a ; número de tile en la tabla de tiles que usaremos ld a, 0 ld [_SPR0_ATT], a ; atributos especiales, de momento nada. ; configuramos y activamos el display ld a, LCDCF_ON|LCDCF_BG8000|LCDCF_BG9800|LCDCF_BGON|LCDCF_OBJ8|LCDCF_OBJON ld [rLCDC], a ; preparamos las variables de la animacion ld a, 1 ld [_MOVX], a ld [_MOVY], a ;preparamos las variables para el sonido ; nota actual ld a, 0 ld [_NOTA], a ; contador retardo musical ld [_CONT_MUS], a ; bucle infinito animacion: ; lo primero, esperamos por el VBlank, ya que no podemos modificar ; la VRAM fuera de él, o pasarán cosas raras .wait: ld a, [rLY] cp 145 jr nz, .wait ; incrementamos las y ld a, [_SPR0_Y] ; cargamos la posición Y actual del sprite ld hl, _MOVY ; en hl, la dirección del incremento Y add a, [hl] ; sumamos ld hl, _SPR0_Y ld [hl], a ; guardamos ; comparamos para ver si hay que cambiar el sentido cp 152 ; para que no se salga de la pantalla (max Y) jr z, .dec_y cp 16 jr z, .inc_y ; lo mismo, minima coord (min Y = 16) ; no hay que cambiar jr .end_y .dec_y: ld a, -1 ; ahora hay que decrementar las Y ld [_MOVY], a jr .end_y .inc_y: ld a, 1 ; ahora hay que incrementar las Y ld [_MOVY], a .end_y: ; vamos con las X, lo mismo pero cambiando los márgenes ld a, [_SPR0_X] ; cargamos la posición X actual del sprite ld hl, _MOVX ; en hl, la dirección del incremento X add a, [hl] ; sumamos ld hl, _SPR0_X ld [hl], a ; guardamos ; comparamos para ver si hay que cambiar el sentido cp 160 ; para que no se salga de la pantalla (max X) jr z, .dec_x cp 8 ; lo mismo, minima coord izq = 8 jr z, .inc_x ; no hay que cambiar jr .end_x .dec_x: ld a, -1 ; ahora hay que decrementar las X ld [_MOVX], a jr .end_x .inc_x: ld a, 1 ; ahora hay que incrementar las X ld [_MOVX], a .end_x: ; un pequeño retardo call retardo ; tocamos la nota siguiente call cambia_nota ; volvemos a empezar jr animacion ; Rutina de apagado del LCD apaga_LCD: ld a,[rLCDC] rlca ; Pone el bit alto de LCDC en el flag de acarreo ret nc ; La pantalla ya está apagada, volver. ; esperamos al VBlank, ya que no podemos apagar la pantalla ; en otro momento .espera_VBlank ld a, [rLY] cp 145 jr nz, .espera_VBlank ; estamos en VBlank, apagamos el LCD ld a,[rLCDC] ; en A, el contenido del LCDC res 7,a ; ponemos a cero el bit 7 (activado del LCD) ld [rLCDC],a ; escribimos en el registro LCDC el contenido de A ret ; volvemos ; rutina de retardo retardo: ld de, 2000 ; numero de veces a ejecutar el bucle .delay: dec de ; decrementamos ld a, d ; vemos si es cero or e jr z, .fin_delay nop jr .delay .fin_delay: ret ; rutina para iniciar el sistema de sonido inicia_sonido: ; activamos sistema de sonido ld a, %10000000 ld [rNR52], a ; iniciamos los volumenes, etc ld a, %01110111 ; SO1 y S02 a tope de volumen ld [rNR50], a ld a, %00000010 ; Canal 2, sale por SO1 y S02 ld [rNR51], a ; canal 2, longitud 63, ciclo 50% ld a, %10111111 ld [rNR21], a ; canal 2, envolvente, volumen inicial maximo, decreciente ld a, %11110111 ld [rNR22], a ; canal 2, longitud activada y valor de la frecuencia alta ld a, %01000110 ; 1 en el bit 6, longitud activa, y ld [rNR24], a ; escribimos %110 en los tres bits altos de la frecuencia. ret cambia_nota: ld a, [_CONT_MUS] ; vemos si hay que tocar la nota o esperar cp a, _TEMPO jr z, .toca_nota inc a ld [_CONT_MUS], a ret .toca_nota: ; reiniciamos el contador ld a, 0 ld [_CONT_MUS], a ; pasamos a tocar la nota ld a, [_NOTA] ; obtenemos el numero de nota a tocar ld c, a ; lo guardamos ld b, 0 ld hl, Notas ; en hl la dirección de las notas add hl, bc ; ahora tenemos la dirección de la nota a tocar ld a, [hl] ; cargamos la Nota ld [rNR23], a ; la escribimos en el registro de frecuencia del canal 2 ; reiniciamos la nota ld a, [rNR24] set 7,a ld [rNR24], a ; pasamos a la siguiente nota y comprobamos si tenemos que reiniciar ld a, c inc a cp a, EndNotas - Notas ; hemos llegado al final? jr z, .resetea_notas ; no, guardamos y volvemos ld [_NOTA], a ret .resetea_notas: ;si, reiniciarmos, guardamos y volvemos ld a, 0 ld [_NOTA], a ret ; Datos de nuestros tiles Tiles: DB $AA, $00, $44, $00, $AA, $00, $11, $00 DB $AA, $00, $44, $00, $AA, $00, $11, $00 DB $3E, $3E, $41, $7F, $41, $6B, $41, $7F DB $41, $63, $41, $7F, $3E, $3E, $00, $00 EndTiles: ; Datos de la musica ; Vamos a usar la quinta octava, porque sus valores comparten los mismos ; tres bits superiores, %110, asi no tendremos que variarlos. Notas: ; 5ª Octava, Do, Re, Mi, Fa, Sol, La, Si (poniendo $6 en freq hi) DB $0A, $42, $72, $89, $B2, $D6, $F7 EndNotas:
And here the ROM if you want to hear: hola-sonido1.gb