Copy Link
Add to Bookmark
Report
LaC's n64 hardware dox... v0.8 (unofficial release)
LaC's n64 hardware dox... v0.8 (unofficial release)
=============================
===updates in this version===
-----------------------------
- Totally new explanation of the pif command structure
- Now included all pif commands I know of.
- Included more stuff on the video interface
-----------------------------
=============================
---------------
release notes
---------------
This little doc is my attempt to make the n64 coding scene a little
better I hope. It is a compilation of stuff i've been working on for some
months... a result of alot of reversing of several games compiled with
libultra, and some help from certain people. It is mainly for people who
wish to code without using a developement library (like libultra). It is
specifically for doing intros,trainers, etc... to attach to roms. It is very
simplistic, and assumes you have some knowledge already. In other words this
doc isnt really meant for people just starting. You should probably have read
the libultra docs and be familiar with the n64 hardware... and the purpose of
things like the AI,PI,SI,VI. And of course you should be knowledgeable about
the r4300i or have some good reference material!
I suppose some emulation authors will find use of this too... especially
the section on the pif... which is far from documented properly in any
developer docs or ameteur docs at this time.
I can't guarantee the 100% accuracy of any of this document. Also I liked
to be greeted if you are using my dox or my source code in your work.
NOTE:
SOME symbols in the follow text will reference symbols #define'd in RCP.H
or r4300.h so make sure you look there when you are confused. Yes I know
they are part of the standard devkit and some people don't have it... but
i'm sure you can find these files if you really need to.
-VI (video interface)
Accessing the video on the n64 is very easy (like most things in this doc)
* initialization:
The video hardware is initialized by simply writing all the necessary
values to the vi regs. I'm only going to discuss one mode here, but u
can easily find the values for other modes by just printing out reg
values to the screen after initing your favorite mode with libultra.
You can also alter values of course to make your own modes. That I will
not discuss here. The one i'm discussing is the simple 320x240 RGBA
16bit non-antialiasing mode.
The base address of the VI regs are mapped at 0xa4400000, so to simply
write a value to a reg in r4300 asm would be like this:
;;this is just an example but it happens to be the write to the
;;VI_H_WIDTH_REG defined in RCP.H
lui at,0xa440 ;at=0xa4400000
li t0,0x140 ;t0=0x140
sw t0,0x8(at) ;write t0 to reg at $at+0x8
in C:
IO_WRITE(VI_H_WIDTH_REG,0x140); //IO_WRITE and IO_READ are in r4300.h
Ok to initialize this mode here are the values to write for each reg:
-> (VI_CONTROL_REG, 0x0000320e)
-> (VI_DRAM_ADDR_REG,0)
-> (VI_H_WIDTH_REG,0x140)
-> (VI_V_INTR_REG,0x2)
-> (VI_V_CURRENT_LINE_REG,0x0)
-> (VI_TIMING_REG,0x03e52239)
-> (VI_V_SYNC_REG,0x0000020d)
-> (VI_H_SYNC_REG,0x00000c15)
-> (VI_H_SYNC_LEAP_REG,0x0c150c15)
-> (VI_H_VIDEO_REG,0x006c02ec)
-> (VI_V_VIDEO_REG,0x002501ff)
-> (VI_V_BURST_REG,0x000e0204)
-> (VI_X_SCALE_REG,0x200);
-> (VI_Y_SCALE_REG,0x01000400)
All the values are pretty obvious and dont need much explaining. And with
a little hacking you can come up with your own modes.
If you've managed to get this far you know that the video screen is just
showing zebra stripes or some garbage. The reason for this is you have
not set the frame buffer for this video mode. This is what
osViSwapBuffer() is for (if you have used libultra). to set your frame
buffer simply write the rdram address of the buffer to VI_DRAM_ADDR_REG
or just change the init code to have it set it up.
Now you can simply write your graphics to the buffer and they'll be
updated.
NOTE: There is ALOT of other things you can tweek, change, by messing with
the VI regs... mess around... figure it out.
If you are getting strange corrupted data make sure you are invalidating
cache lines etc... (read the r4300i docs on the cache)
If you are confused about physical and virtual addresses or how the cache
works, read the r4300 man... and if you still don't understand it totally,
join the club.
* end init
* vertical retrace wait:
(Cheap Way) normally you would wait on the interrupt.
The VI interrupt occurs on every v_blank
while ( VI_CURRENT_REG != 512 ) {wait}
* end retrace wait
* detailed info on VI regs
_- Register bit patterns and operational description -_
REGISTER #0 VI_CONTROL_REG [16:0]
[1:0] pixel_size
0: blank (no data, no sync)
1: reserved
2: 5/5/5/3 ("16" bit - really 18 bit)
3: 8/8/8/8 (32 bit)
[2] gamma_dither_enable (normally on, unless "special effect")
[3] gamma_enable (normally on, unless MPEG/JPEG)
[4] divot_enable (normally on if antialiased, unless decal lines)
[5] vbus_clock_enable (off always)
[6] serrate (always on if interlaced, off if not)
[7] test_mode (for diagnostics, not used in normal operation)
[9:8] aa_mode[1:0] (anti-alias mode)
0: aa & resamp (always fetch extra lines)
1: aa & resamp (fetch extra lines if needed)
2: resamp only (treat as all fully covered)
3: neither (replicate pixels, no interpolate)
[11] kill_we (for diagnostics, not used in normal operation)
[15:12] pixel_advance (always 3 for optimal operation)
[16] dither_filter_enable (normally on for 16 bit, off for 32 bit)
REGISTER #1 VI_DRAM_ADDR_REG
[23:0] Frame buffer offset
REGISTER #2 VI_H_WIDTH_REG
[11:0] Frame buffer line width in pixels
REGISTER #3 VI_V_INTR_REG
[9:0] Interrupt when current half-line = V_INTR
REGISTER #4 VI_V_CURRENT_LINE_REG
[9:0] Current half line, sampled once per line
(the lsb of v_current is constant within
a field, and in interlaced modes gives the
field number - this is constant for non-
interlaced modes)
Writing to this will clear the interrupt line.
REGISTER #5 VI_TIMING_REG
[29:20] burst_start (Start of color burst in pixels from hsync)
[19:16] vsync_width (Width if vsync in half-lines)
[15:8] burst_width (Width of color burst in pixels)
[7:0] hsync_width (Width of hsync in pixels)
REGISTER #6 VI_V_SYNC_REG
[9:0] Number of half-lines per field
REGISTER #7 VI_H_SYNC_REG
[20:16] 5-bit pattern used for PAL video only
[11:0] h_sync_period (the total duration of a line in 1/4 pixels)
REGISTER #8 VI_H_SYNC_LEAP_REG
[27:16] Identical to h_sync_period (except for PAL)
[11:0] hsync_leap_b (identical to h_sync_period (except for PAL))
REGISTER #9 VI_H_VIDEO_REG
[25:16] h_video_start (start of active video in screen pixels)
[9:0] h_video_end (end of active video in pixels from hsync)
REGISTER #10 VI_V_VIDEO_REG
[25:16] v_video_start (start of active video in screen half-lines)
[9:0] v_video_end (end of active video in half-lines from vsync)
REGISTER #11 VI_V_BURST_REG
[25:16] v_burst_start (start of color burst enable in half-lines)
[9:0] v_burst_end (end of color burst enable in half-lines)
REGISTER #12 VI_X_SCALE_REG
[27:16] x_offset (horizontal subpixel offset (2.10 format))
[11:0] x_scale (1/horizontal scale up factor (2.10 format))
REGISTER #13 VI_Y_SCALE_REG
[27:16] y_offset (vertical subpixel offset (2.10 format))
[11:0] y_scale (1/vertical scale up factor (2.10 format))
REGISTER #14 UNKNOWN OPERATION
[6:0] test_addr (diagnostic use only)
REGISTER #15 UNKNOWN OPERATION
[31:0] test_data (diagnostic use only)
NOTE: remember that all numerical values for the regs described above
start with the first value being 0!
_- Register Initial value -_
All registers have initial value when the n64 is turned on except for
these:
VI_V_INTR_REG = 0x3FF
VI_H_SYNC_REG = 0xD1
VI_H_SYNC_REG = 0xD2047
* end detailed info on VI regs
-AI (audio interface)
The audio interface is probably the easiest thing to do.
* initialization:
None needed. Altho I suppose you can include setting the sound frequency
and/or enabling the AI_CONTROL_REG as initialization (step #4 below)
* end init
* setting frequency:
1. Set the dac rate
write to AI_DACRATE_REG this value: (VI_NTSC_CLOCK/freq)-1
^could be pal
2. Set the bitrate (4bit, 8bit, or 16bit)
write to the AI_BITRATE_REG the value: bitrate-1
* end frequency
* sending sound buffer
1. Before sending a buffer, its a good idea to make sure one isnt already
being played. Simply read AI_STATUS_REG then AND it with
AI_STATUS_FIFO_FULL. if the result is true... then wait.
2. Write the 64bit aligned address of the sound buffer to AI_DRAM_ADDR_REG
3. Write the length of the buffer be played to AI_LEN_REG
NOTE: this length must be multiple of 8, no larger than 262144 bytes.
4. Write AI_CONTROL_DMA_ON to the AI_CONTROL_REG (only needed once)
* end sound buffer
NOTE: If you have read the libultra manuals you will notice when it
discusses the AI routines (osAi.man) it says the AI regs are double
buffered. This is important to realize that you can send one buffer
while another is playing. Also note that the AI_LEN_REG counts down
to 0 as the current buffer is being played. This can be useful to tell
how much of the buffer is left.
-PI (peripheral interface)
* init pif: ( you should do this before you do anything! )
1. Simply write 0x8 to PIF_RAM_START+0x3c
* end init pif
* PI DMA transfer
1. Wait for previous dma transfer to finish (see next explanation)
2. Write the physical dram address of the dma transfer to PI_DRAM_ADDR_REG
NOTE: To convert from a virtual address to physical, simply
AND the address with 0x1fffffff.
3. Write the physical cart address to PI_CART_ADDR_REG.
4. Write the length-1 of the dma transfer to PI_WR_LEN_REG
this is from cart->rdram change this to RD ^ for the other way
also note you must write a 0x2 to PI_STATUS_REG in order to write to
the cart space (0xb0000000)
NOTE: The cart addr must be 2 byte aligned, and the rdram addres must
8-byte aligned. Once again make sure you write back the cache lines
and invalidate the cache lines if needed, or you will run into
trouble.
* end PI DMA transfer
* PI DMA wait
1. Read PI_STATUS_REG then AND it with 0x3, if its true... then wait until
it is not true.
NOTE: Look at RCP.H for more information on the PI_STATUS_REG and the PI
in general.
* end PI DMA wait
-reading and writing the new sram chip (DS1) -
Sram is mapped at 0xa8000000.
The trick is that you cannot write to it directly, you must us the PI.
Actually it is possible to write to it directly, but I dont know how because
it needs to be timed carefully.
Its a little tricky which requires writing some values into some PI regs
to initialize the PI correctly for the type of transfer protocol the sram
needs for successful data transfer.
* Init the PI for sram
1. write 0x05 to the PI_BSD_DOM2_LAT_REG.
2. write 0x0c to the PI_BSD_DOM2_PWD_REG.
3. write 0x0d to the PI_BSD_DOM2_PGS_REG.
4. write 0x02 to the PI_BSD_DOM2_RLS_REG.
* End init PI for sram
Now you should be able to use the PI to transfer between rdram and sram.
(refer to the dox above concerning PI, but replace the ROM address with
sram address 0xa8000000).
-SI (serial interface)
The SI is very similar to the PI for obvious reasons. It is used mainly for
accessing the pifram... which will be dicussed in the next section.
* SI DMA transfer
1. Wait for previous dma transfer to finish (see next explanation)
2. Write the physical dram address of the dma transfer to SI_DRAM_ADDR_REG
3. Write PIF_RAM_START to the SI_PIF_ADDR_RD64B_REG or the
SI_PIF_ADDR_WR64B_REG, depending on what you wish to do (read or write).
This will cause a 64B read or write between pif_ram and rdram.
NOTE: The SI addr must be 2 byte aligned, and the rdram addres must
8-byte aligned. Once again make sure you write back the cache lines
and invalidate the cache lines if needed, or you will run into
trouble.
* end SI DMA tranfer
* SI DMA wait
1. Read SI_STATUS_REG then AND it with 0x3, if its true... then wait until
it is not true.
NOTE: Look at RCP.H for more information on the SI_STATUS_REG and the SI
in general.
* end SI DMA wait
-PIF Usage-
*New in v0.7*
In previous versions of this document I regret to say that when I explained
how the pif command structure works I really goofed up. Alot of the
information was based on speculation and was not tested enough to be
proven. In this version I explain a totally different understanding of how
the pif command processing is done by the pif chip. So if you are going
off the information from previous dox, I seriously suggest you read this
section again. It is much more detailed and logical now.
If you have done research and peeked at the RCP.h file you should
already know some things about the pif. The SI is used to send commands
to the pif ram that tell the pif what to do. The SI is also used to read
the results of those commands back. You can tell the pif to do alot of
stuff. for instance... reading joysticks, reading mempacks, detecting
joysticks, detecting mempacks, activating the rumblepack, detecting the
rumble pack, reading cartridge eeprom... etc.
Below is a detailed view of pif command structure processing and an example
of using them to perform some operations.
At First, this is how pif ram should be visualized:
|{Diagram 1.0}|
[64byte block] at 0xbfc007c0 (1fc007c0)
{
00 00 00 00 : 00 00 00 00 - 8 bytes
00 00 00 00 : 00 00 00 00 - 8 bytes
00 00 00 00 : 00 00 00 00 - 8 bytes
00 00 00 00 : 00 00 00 00 - 8 bytes
00 00 00 00 : 00 00 00 00 - 8 bytes
00 00 00 00 : 00 00 00 00 - 8 bytes
00 00 00 00 : 00 00 00 00 - 8 bytes
00 00 00 00 : 00 00 00 00 - 8 bytes
} ^^pif status/control byte
Commands are processed from any byte in pifram.
ie: The pif chip steps thru each byte to load commands.
Each command has a structure like so:
byte 1 (t) = x number of bytes to send to pif chip
byte 2 (r) = x number of bytes to recieve from pif chip
byte 3 (c) = Command type
- Command Processing -
The pif chip constantly looks at the last byte of pifram and uses it sort
of as a semaphore to decide what to do with pifram. This last byte I will
refer to as the status byte or the control byte. If the pif chip sees the
control byte has been set to 01 (bit 0) it will know there are commands
waiting to be processed in pifram. After it has decided this, it goes
through each byte of pifram in a scanning process in order to execute
commands. The Scanning process is described below:
while(not end of pifram)
t = get byte
if t >= 0 then
r get byte
send next t number of bytes to pif chip
do pif chip command execution
if executed then
get r bytes from pif chip
end if
increment channel
else if t==fe then
exit while loop
end if
end while
The t value is very important to the processing. If it is a negative value
then it does basically nothing but read the next byte into t again. If it
is a positive value or zero we know it needs to do something. It now gets
the next byte from pifram and puts it into r. r is used later to tell how
much data to get back from pifram. Next it sends all the bytes needed for
the command to the pif chip... t being the x number of bytes to send.
The pif chip now has all the data it needs to _attempt_ to execute a
command. If we sent 0 bytes to the pif chip it will not execute a command
and we will recieve no bytes from the pif chip in r. But the channel
counter (I will explain channels later) will increment even if no command
was really executed. A t value of 0 is basically a null command.
When the pif chip executes certain commands it expects certain limits on
values for t and r. Usually these values are fixed. If the values are
not what it expects it will attempt to process the command. If it could not
process the command it will let you know by setting the r value in pifram
with an error value I will describe later. It is a good idea to realize
that bits 6 and 7 of the r value are not to be used by the pif command.
These are actually the error bits. ie: MASK r &= ~0xC0
|{Diagram 1.0b}|
Command Types:
| Command | Description |t |r |
+---------+--------------------------+-----+
| 00 | request info (status) |01|03|
| 01 | read button values |01|04|
| 02 | read from mempack slot |03|21|
| 03 | write to mempack slot |23|01|
| 04 | read eeprom |02|08|
| 05 | write eeprom |10|01|
| ff | reset |01|03|
NOTE: values are in hex
-Channels-
The pif commands operate on certain channels in the n64. The channels can
also be refered to as ports. The n64 has 6 channels to my knowledge.
The first 4 channels (channels 0-3) are the joystick ports. The last two
channels (4 and 5) I am pretty sure are in the cartridge port. Carts
that have eeprom have it at either channel 4 or 5 or both. There are
some carts that have two eeprom chips in them, and I believe channel 5 is
used for the 2nd eeprom in these carts... Although I have not tested this
theory.
To get a better understanding of how all this works, here is an example on
how to build a command for reading a joystick:
* Init the joysticks for reading
|{Diagram 1.1}|
Send the pif command block to pifram using the SI DMA
-----------------------------++------------------------------
such a block to read 4 joys: || such a block to read 1 joy:
[64byte block] || [64byte block]
{ command data || {
joy1 ff 010401 - ffffffff || joy1 ff 010401 - ffffffff
joy2 ff 010401 - ffffffff || ff ffffff - ffffffff
joy3 ff 010401 - ffffffff || ff ffffff - ffffffff
joy4 ff 010401 - ffffffff || ff ffffff - ffffffff
fe 000000 - 00000000 || fe 000000 - 00000000
00 000000 - 00000000 || 00 000000 - 00000000
00 000000 - 00000000 || 00 000000 - 00000000
00 000000 - 00000001 || 00 000000 - 00000001
} || }
-----------------------------++------------------------------
After sending this the joystick values will now be updated in pif RAM
NOTE: the ffffffff is put into the data column just for filling the
space that the pif chip will write the data to. You can put
anything there because the pifchip will never try to process it,
because it skips past all bytes it writes the r bytes to.
0xff
|
0xff is padding so that the joystick values end up in the 2nd column,
this is done because it is convenient to align the data when reading
the pifram back to rdram. It is not necessary to align it, so this
ff is not really needed. IE: 010401ff would work just fine, but the
joystick values would start at the 4th byte rather than the 5th.
0x010401 is the command that reads the joystick values.
|
t 0x01 says we are going to send 1 byte (the command type).
r 0x04 says we are going to read 4 bytes (into the data column)
c 0x01 is the command type (read button values).
Here is the step by step process in which the pif chip processes this
command block:
NOTE: channel always starts out at 0.
1. reads in ff into t
tx < 0 so it does nothing
2. reads in 01 into t
3. reads in 04 into r
4. send tx number of bytes to pif chip (just send the command 01)
5. pif chip executes the command sucessfully and sends no error bytes
6. pif chip sends r bytes back to pifram
7. pif chip increments the channel.
8. pif chip now skips over the r bytes it wrote and then
basically returns to step 1
9. once it hits fe or is at the end of pifram, it sets the control
byte to 0 and exits.
* end Init joysticks
NICE INFO:
The 0x01 (diagram 1.1) tells the pif there is a new command block to be
processed. Without this the command block will not be executed. You will
notice that the reason for the one being there is actually that it is
being written to the pif's status control. This may not be actually what
it is called but it seems to serve a similar function. This is the same
byte you are writing to when you initialize the pif (see: * init pif).
You will also notice that this byte will be set to 0x80 after holding
down the reset button. 0x80 means the pif is busy. The value will be
set to 0x00 after you let go of the reset button or .5 seconds passes
(whichever comes first).
After this .5 seconds a NMI will be generated which will reset the
r4300 and the n64. Also note that this byte is set to 0x00 once a
command has been executed by the pif. This is why when you read the pif
ram after sending a command the last byte is no longer 1, but 0. If it
is still 1 after reading it back, then you know your command didnt
execute.
* Read Joysticks
The joy values can be read from the spaces marked by 0xFFFFFFFF in the
block above. Of course you must first DMA from pif ram back to rdram.
Or you can just read the data directly by making a pointer to
0xbfc007c0 (start of the pif_ram), although I would not recommend that
method.
Here would be a sufficient C code to read in a controller's values:
void siReadJoy(int cont,OSContPad *p)
{
unsigned char pif_block[64];
si_DMA_from_pif (pif_block);
memcpy (p,pif_block+((cont*8)+4),4);
}
The OSContPad structure is in the libultra header file OS.H
* end Read Joysticks
* Detecting if Joysticks are connected
This is very easy and can be done after you send any command to read
or write something to the controllers. Whenever you try and execute a
command on a channel and that device on the channel (like a joystick)
is not present the pif will write an error value to the r byte of the
command that the error occured in. For instance... lets say you did the
example above and you tried to read controller values. Well if you read
the controller values for all four joystick channels you will notice that
if you don't have a joystick physically plugged in to the port(s) you are
reading from, then no values will appear. Well I think this is an obvious
result. But also notice that the pif will put an error value into the r
byte of the command.
The Error values are as follows:
0x00 - no error, operation successful.
0x80 - error, device not present for specified command.
0x40 - error, unable to send/recieve the number bytes for command type.
ie: bits 6 and 7 of the r value are never used in commands. MASK: 0xC0
This would be an example of the result of trying to read 4 controllers
(like in above example) and only a joystick in port 3 is connected:
|{Diagram 1.2}|
-----------------------------------+
[64byte block] read from pif ram |
{ command data |
joy1 ff018401 - ffffffff <--- 8 is the error code for device
joy2 ff018401 - ffffffff | not present.
joy3 ff010401 - 00000000 <--- read was successful on this
joy4 ff018401 - ffffffff | channel, no buttons being pressed
fe000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
} |
-----------------------------------+
This would be an example of the result of trying to read 5 bytes for the
read joystick command: (all 4 joysticks are connected)
|{Diagram 1.3}|
-----------------------------------+
[64byte block] sent to pif ram |
{ command data |
joy1 ff010501 - ffffffff <---
joy2 ff010501 - ffffffff <--- note we tried to read 5 instead
joy3 ff010501 - ffffffff <--- of 4. The device only allows you
joy4 ff010501 - ffffffff <--- to read 4 bytes with that command
fe000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000001 |
} |
-----------------------------------+
-----------------------------------+
[64byte block] read from pif ram |
{ command data |
joy1 ff014501 - 00000000 <--- (note that no buttons are being
joy2 ff014501 - 00000000 <--- pressed on any controller)
joy3 ff014501 - 00000000 <--- notice the 4. It is the error
joy4 ff014501 - 00000000 <--- code for send/recieve.
fe000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
} |
-----------------------------------+
NOTE: Even though we tried to read an extra byte for the buttons values
the button values will still appear... but the error code will
still be generated because there is only 4 bytes to be read, not
5.
* end Detecting if Joysticks are connected
* Getting controller status
0x010300 is the command used to get the controller status.
|
0x01 says we are going to send 1 byte (the command type).
0x03 says we are going to read 3 bytes (into the data column)
0x00 is the command type (get controller status).
Here is an example of reading the status from 4 controllers.
Only the first two controllers are actually plugged in.
There is a pack in the 1st controller and there is no pack in the second
controller.
|{Diagram 1.4}|
-----------------------------------+
[64byte block] sent to pif ram |
{ command data |
joy1 ff010300 - ffffffff |
joy2 ff010300 - ffffffff |
joy3 ff010300 - ffffffff |
joy4 ff010300 - ffffffff |
fe000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000001 |
} |
-----------------------------------+
-----------------------------------+
[64byte block] read from pif ram |
{ command data |
joy1 ff010300 - 050001ff <--- notice only 3 bytes were read
joy2 ff010300 - 050002ff <--- that is why the last byte is
joy3 ff018300 - ffffffff | still ff
joy4 ff018300 - ffffffff |
fe000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
} |
-----------------------------------+
The first two bytes in the data column is the controller type. I'm not
exactly sure what use this is... do steering wheels have a different
controller type? I don't know.
The 3rd byte is useful. Its for detecting if there is something plugged
into the mempack slot on the controller.
1 = something is plugged into the mempack slot
2 = nothing plugged in
4 = pad address crc from controller slot read/write (mempack slot)
* end Getting controller status
* Resetting the controllers
WARNING: Some of this info could be wrong, I have yet to test fully.
This command is performed by the osContReset() function in libultra.
0x0103ff is the command for resetting
|
t 0x01 says we are going to send 1 byte (the command type).
r 0x03 says we are going to read 3 bytes (into the data column)
c 0xff is the command type (reset).
As stated in the osContReset() man page... this command will reset all
the joysticks and return the values to a neutral position. I know this
is needed for calibration. For instance, if you turn on the n64 with
the analog stick held in an off-center position then the values will
be off-center until the controller is reset. What is also nice about
this command is that it can be used to not only reset the controllers
but grab the status of the controllers at the same time. This is why
we are reading three bytes (just like in the example above). I am not
sure what happens when you try this command on channels >= 4.
See Diagram 1.4
* End Resetting the controllers
* reading/writing cart eeprom
The cart eeprom is better know as the cart sram... its the little
eeprom chip that is in alot of the 1st generation cartridges and some
of the newer cartridges. Although people call it sram, it really isn't.
Its eeprom, which is accessed much differently that sram. Reading and
writing sram is described in the PI section of this document.
Eeprom is also what is in the mempacks, which is described in the next
section. Cart eeprom is written to using pif commands. The way commands
and data are formatted in pifram is no different than the way that they
are done for accessing the controller ports.
The first 4 bytes are zero because this is not a controller command and
we will not be accessing the 4 joystick channels. IE: when the pif chip
processes a command block, if it reads a 0 in t then it knows it will
be executing a NOP command and incrementing the channel counter.
-Probing the eeprom-
The first thing you must do is test to see if the eeprom is there.
Normally you could test if the eeprom is there by just attempting to
write some bytes to it and reading them back. Of course we do not want
to do this because eeprom contains important save-game data we would not
like to destroy just for a simple test. So we will have to "probe" the
eeprom to detect if its there. You do this in a similar way that you
get the status of the controllers.
Here is an example pif block to send to the pif ram to probe for eeprom:
|{Diagram 1.5}|
The cartridge is assumed to have 512 bytes of eeprom in it.
----------------------------------+
[64byte block] sent to pif ram |
{ |
00000000 - ff010300 <-- this command should look familiar.
ffffffff - fe000000 | notice when sending any command
00000000 - 00000000 | for eeprom we should have made
00000000 - 00000000 | sure 4 other channels have been
00000000 - 00000000 | processed.
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000001 |
} |
----------------------------------+
----------------------------------+
[64byte block] read from pif ram |
{ |
00000000 - ff010300 |
008000ff - fe000000 <-- this result shows that there is
00000000 - 00000000 | an eeprom present.
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
} |
----------------------------------+
The 3 byte status is read into the left column (3rd word).
the 0x8000 from that read tells us that there is eeprom there. If the
cartridge did not have any eeprom in it the buffer read from pif ram
would of looked like:
|{Diagram 1.6}|
----------------------------------+
[64byte block] read from pif ram |
{ |
00000000 - ff018300 <-- tells us there was no eeprom there
ffffffff - fe000000 | to probe. (error bits)
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
} |
----------------------------------+
-Writing cart eeprom-
The command for writing the cart eeprom is
0x0a0105xx
|
t 0x0a says we are going to send 10 bytes to the pif (command+offset+data)
r 0x01 says we are going to get 1 byte (the error byte)
c 0x05 is the command type (write eeprom)
xx is the offset to write to
^^ this value can differ depending on the size of the eeprom. The offset
is the number of the 8-byte block to write to. ie: if you have 512 bytes
of eeprom in the cart, then this value can be anywhere between 0x0 and
0x40.
|{Diagram 1.7}|
The cartridge is assumed to have 512 bytes of eeprom in it.
----------------------------------+
[64byte block] sent to pif ram |
{ |
00000000 - 0a010521 <-- this command will write the next
deadbeef - a5b6c7d8 | 8 bytes to block 0x21 in eeprom.
ffffffff - fe000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000001 |
} |
----------------------------------+
----------------------------------+
[64byte block] read from pif ram |
{ |
00000000 - 0a010521 |
deadbeef - a5b6c7d8 |
00ffffff - fe000000 <-- the 00 written notifies that the
00000000 - 00000000 | eeprom was written to. This value
00000000 - 00000000 | is that 1 extra error byte that
00000000 - 00000000 | the command specifies.
00000000 - 00000000 |
00000000 - 00000000 |
} |
----------------------------------+
-Reading cart eeprom-
The command for reading the cart eeprom is
0x020804xx
|
t 0x02 says we are going to send 2 bytes to the pif (command+offset)
r 0x08 says we are going to get 8 bytes (1 block read from eeprom)
c 0x04 is the command type (read eeprom)
xx is the offset to read from
^^ this value can differ depending on the size of the eeprom. The offset
is the number of the 8-byte block to read from. ie: if you have 512
bytes of eeprom in the cart, then this value can be anywhere between
0x0 and 0x40.
|{Diagram 1.8}|
The cartridge is assumed to have 512 bytes of eeprom in it.
----------------------------------+
[64byte block] sent to pif ram |
{ |
00000000 - 02080409 <-- this command will read 0x08 bytes
ffffffff - fe000000 | from block 0x09 in eeprom.
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000001 |
} |
----------------------------------+
----------------------------------+
[64byte block] read from pif ram |
{ |
00000000 - 02080409 |
deadbeef - a5b6c7d8 <-- the eight bytes read are stored
00000000 - 00000000 | here.
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
00000000 - 00000000 |
} |
----------------------------------+
* end reading/writing cart eeprom
* reading/writing mempack port
Important NOTE: As far as I know... mempack eeprom can be only 32k in
size. Every address beyond 32768 is out of the
mempack address range and is most likely data not
meant for mempack eeprom... but most likely meant for
the Rumblepack or some other peripheral that is
connected to the controller.
The command for reading from the mempack is
0x032102 xxxx
|
t 0x03 says we are going to send 3 bytes to the pif (command+16bit_offset)
NOTE: the offset also contains a 5-bit address CRC. The offset must
be aligned on a 32 byte boundry, so the address crc is in the
low 5 bits. Also the address must be shifted right 5 bits. The
address is really similar to the cart eeprom address in that it
is an address to a block, but in this case it is a 32 byte
block.
r 0x21 says we are going to get 33 bytes, that is one 32-byte-block read from
the mempack slot + 1 byte for the data CRC (described later)
c 0x02 is the command type (read mempack slot)
xxxx is the offset to read from
The command for write to the mempack is
230103xxxx
|
t 0x23 says we are going to send 35 bytes to the pif
(command + 2-bytes-offset + data)
NOTE: the offset also contains a 5-bit address CRC. The offset must
be aligned on a 32 byte boundry, so the address crc is in the
low 5 bits. Also the address must be shifted right 5 bits. The
address is really similar to the cart eeprom address in that it
is an address to a block, but in this case it is a 32 byte
block.
r 0x01 says we are going to get 1 byte for the data CRC (described later)
c 0x03 is the command type (read mempack slot)
xxxx is the offset to read from
* end reading/writing mempack port
* mempack port checksum
Whenever you write / read data you must include an address and data
CRC. These are stored in two different locations in the command
structure (see the two commands above).
These CRC algorithms are described below.
NOTE: this is the data CRC when something is plugged into the mempack
port. The data you get read or write to the mempack port obvious will
need or have a data CRC... but when something is not plugged into the
mempack port, then the CRC is calculated the same way except the final
8-bit result is NOT'd. You might wonder why there would even be a CRC
when reading from the mempack port and nothing is plugged in. Well
basically when nothing is plugged in, data is still read, but it is not
valid data. This data is usually all 0's. The data CRC algo is still
calculated but the result is of course NOT'd to let you know that the
data is erronous.
*******Data CRC routine (written in Pascal for pseudo-code purposes) ********
function ContDataCrc (data: PByteArray): byte; { PByteArray is a byte pointer }
var
temp,temp2 : byte;
i,j : Integer;
begin
temp:=0;
for i := 0 to 32
do begin
for j := 7 downto 0
do begin
if ((temp and $80)<>0) then temp2 := $85 else temp2 := $00;
temp := temp shl 1;
if (i = 32)
then begin
temp := temp or 0;
end
else begin
if (((data[i]) and ($01 shl j))<>0)
then temp:=(temp or 1) else temp:=(temp or 0);
end;
temp := (temp xor temp2);
end;
end;
ContDataCrc:=temp;
end;
Address CRC routine:
*******Addr CRC routine (written in Pascal for pseudo-code purposes) ********
function PackAddrCRC (int16 addr): byte;
var
t,t2 : byte;
i,j : Integer;
begin
t:=0;
for i := 0 to 15
do begin
if ((t and $10)<>0) then t2 := $15 else t2 := $00;
t := t shl 1;
if ((addr and $400)<>0) then t := (t or $1)
addr := addr shl 1;
t := t xor t2;
end
PackAddrCRC:=(t and $1f);
end;
* end mempack port checksum
* Rumblepak
The Rumblepack, like the Mempack connects to the mempack port on the
bottom off the n64 controller. Therefore you read/write to the mempack
port in order to gain access to the rumble pack.
-Checking for connection-
In order to check if what is plugged into the mempack port is a rumble
pack or a mempack, you need to do the following:
1. Use the controller command to get the status (see Diagram 1.4)
This way you will at least know something is plugged into the port.
Or you could just skip to step two and if the data crc you get back
is NOT'd you will know nothing is plugged in.
2. Using the mempack read command, read offset block 0x400. This
offset would be written as 0x8001 in the actual command. Because,
remember you are storing the address crc in the lower 5 bits.
3. If the data you get is all 0x80's then you know a Rumblepack is
there. If the data is 0x00's then its not a Rumblepack and is most
likely a Mempack.
-Rumbling-
The Rumblepack rumbles based on two values... OFF and ON. There is no
intensity values involved in rumbling. The whole idea is that the
slower you turn it off and on, the higher the intensity rumble and vice
versa.
To turn the Rumblepack on you simply write a 32 byte block of 01's
to offset 0x600. And to turn it off you write 00's instead. Its as
simple as that... But remember you are using the standard mempack
port writing commands so all the rules apply (CRC'ing etc...).
* end Rumblepak
--------
Future
--------
I know this document isnt much as it stands but I plan on adding some rsp
info into it and of course any other info I currently havent included as time
permits.
Also all my source code for the stuff in this doc might get released.
Right now everything is meant to build with SN's assembler and linker. i wish
to recode it so it compiles with a freeware assembler... so once I do that I
will release source... or maybe before.
__-----------------------------------------------__
greets to people who helped me with some stuff!
__-----------------------------------------------__
nagra, bpoint, hartec, jovis, wild_fire, datawiz, zilmar
Questions & Comments: about anything except where to get a devkit or libultra
or roms or header files or whatever. In other words if you have a question
about stuff in this doc and you are fairly intelligent, or you have a question
about how to implement things in your n64 program or emulator...
contact LaC on IRC efnet in #n64dev
or if you must:
EMAIL's:
LaC@dextrose.com
LaC@nemu.com
EOF