Introducción

    NINTENDO
   ______                     ____             
  / ____/___ _____ ___  ___  / __ )____  __  __
 / / __/ __ `/ __ `__ \/ _ \/ __  / __ \/ / / /
/ /_/ / /_/ / / / / / /  __/ /_/ / /_/ / /_/ / 
\____/\__,_/_/ /_/ /_/\___/_____/\____/\__, /  
                                      /____/   
			Programming Tutorial
			 By David Pello (ladecadence.net)

             _n_________________     
            |_|_______________|_|    
            |  ,-------------.  |    
            | |  .---------.  | |    
            | |  |         |  | |    
            | |  |         |  | |    
            | |  |         |  | |    
            | |  |         |  | |    
            | |  `---------'  | |   
            | `---------------' |  
            |   _ GAME BOY      |    
            | _| |_         ,-. |    
            ||_ O _|   ,-. "._,"|    
            |  |_|    "._,"   A | 
            |      _  _  B      |
            |     // //         |    
            |    // //  \\\\\\  |    
            |    `  `    \\\\\\ ,    
            |________...______,"     

Nintendo Gameboy (DMG)

To begin this tutorial development for gameboy, let's review a little the technical characteristics of our wonderful console:

  • CPU: 8-bit Sharp LR35902 (Similar to the Z80) to 4.19 MHz MHz
  • 8K RAM main
  • 8K RAM Video
  • 2.6 "LCD 160 × 144, 4 Tones
  • Stereo, 4 channels
  • Serial port (ext)
  • 6V, 0.7A
  • 90 mm x 148 x 32 mm

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.

GBz80

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.

Registros

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.

Flags

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:

Zero Flag (Z)

This bit is set to 1 if the operation result was zero (0). Widely used in conditional jumps.

Carry flag (C, or Cy)

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.

16-bit registers

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.

Memory Maps

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.

Organizing a GameBoy ROM

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.

Video System

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:

$ FF40 - LCDC - LCD Control (R / W)

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:

Bit 7 - Control Display

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

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.

$ FF41 - Status - status LCD (R / W)

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.

$FF42 - SCY - Scroll Y (R/W), $FF43 - SCX - Scroll X (R/W)

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.

$FF4A - WY - Y position of the window (R / W), $ FF4B - WX - Position X of (R / W) Window least 7

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.

Funds

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:

  • Byte 0-1 - First line (8 pixels)
  • Byte 2-3 - Second line
  • Byte 14 to 15 - Eighth line.

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.

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:

Byte 0 - Y coordinate

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.

Byte 1 - X coordinate

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

Byte 2 - Number of sprite

Specifies the sprite number to use for this entry sprites defined in Table $ 8000-8FFF.

Byte 3 - Attributes

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.

Tools

Assembler

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

Editor

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

Emulator

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.

Hello World

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

Code

Taking this data to draw our smiling face, we started with the program:

holamundo.asm
;  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.

Explanation

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 , where destination and origin can be registers, memory addresses or direct numbers.

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 dec, which simply subtracts a unit to record to say it. As we have copied a byte, and because there are only 15 per copy, so subtract one ab, to reflect this.

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.

Improvements

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:

holamundo2.asm
; 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:

Hello sprites

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:

holasprite.asm
; 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:

Hello JoyPad

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:

  • Turn off the pad and buttons
  • Read the last four bits of the record pad in A (with what we have in the state A directional PAD)
  • Zero the upper 4 bits of A (only care about the 4 low)
  • Exchanging the 4 low bits of A by high (low remaining to us free again and the directional pad being remembered on high)
  • A temporary move to another record (for example B)
  • Enable and disable buttons PAD
  • Read the last four bits of the record pad in A (with what we have to the state of the buttons)
  • Zero the upper 4 bits of A (only care about low)
  • Make an OR with temporary registration (B), thus, to take the status of all (the pad in the 4 high bits, and buttons on the 4 low) buttons
  • Get the complement of A, which changes the 0's by 1's and 1's for zeros, so we pressed a few buttons instead of the other way around
  • To save a variable in memory.

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:

holajoypad.asm
; 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:

Hello Window

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:

holaventana.asm
; 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:

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

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

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:

hola-ventana.gb

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.

Hello Timer

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:

holatimer.asm
; 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:

Explanation

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.

Hola 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:

holabancos.asm
; 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:

Hello Sound

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.

  • Channel 1: Channel 1, Channel is a square wave with changeable cycle work, surround and portamento.
  • Channel 2: A channel square wave with changeable work cycle, with surround.
  • Channel 3: Channel programmable wave with a RAM table 32 steps.
  • Channel 4: Channel surround white noise.

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:

Channel 1 - tone and portamento

$FF10 - NR10 - Registration portamento Channel 1 (R / W)

 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

$FF11 - NR11 - Channel 1 time / duty cycle (R / W)

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.

$FF12 - NR12 - Channel 1 - Surround (R / W)

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

$FF13 - NR13 - Channel 1 - Frequency (low) (W

Low 8 bits of the 11 bit frequency (x). The following 3 bits are NR14 ($ FF14)

$FF14 - NR14 - Channel 1 - Frequency (hi) (R / W)

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

Channel 2 - Tone

This channel works just like 1, but has no record of portamento.

$FF16 - NR21 - Channel 2 Length / Duty Cycle (R / W)

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.

$FF17 - NR22 - Channel 2 Surround (R / W)

 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

$FF18 - NR23 - Channel 2 - Frequency (what) (W)

Low 8 bits of the 11 bit frequency (x). The following 3 bits are NR24 ($ FF19)

$FF19 - NR24 - Channel 2 - Frequency (hi) (R / W)

 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

Channel 3 - Programmable Wave

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.

$FF1A - NR30 - Channel 3 on / off (R / W)

Bit 7 - Turns the channel (0 = Stopped, 1 = Plays) (R / W) 

$Ff1b - NR31 - Channel 3 - Length

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.

$Ff1c - NR32 - Channel 3 - Output level (R / W)

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) 

$Ff1d - NR33 - Channel 3 - Frequency (what) (W)

Low 8 bits of the channel frequency (x).

$FF1E - NR34 - Channel 3 - Frequency (hi) (R / W)

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

$FF30-$FF3F - Wave Memory

It contains the values ​​for generating the wave.

This memory is organized into 32 4-bit values ​​that reproduce high 4 bits first.

Channel 4 - Noise

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.

$FF20 - NR41 - Channel 4 - length (R / W)

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.

$FF21 - NR42 - Channel 4 - Surround (R / W)

 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

$FF22 - NR43 - Channel 4 - polynomial counter (R / W)

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

$FF23 - NR44 - Channel 4 - Accountant; Trigger (R / W)

 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) 

General records sound control

$FF24 - NR50 - Volume control / 5th Channel (R / W)

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.

$FF24 - NR50 - Volume control / 5th Channel (R / W)

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 

$FF26 - NR52 - Sound On / Off

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.

Example

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.

holasonido.agb
; 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

 
tutorial_de_ensamblador.txt · Última modificación: 2012/11/04 10:32 por zako
 
Excepto donde se indique lo contrario, el contenido de esta wiki se autoriza bajo la siguiente licencia:CC Attribution-Noncommercial-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki