Home | New Tutorials | Programs | ||
---|---|---|---|---|
1-R4DS Setup | 2-Software | Space Invaders | ||
Old Tutorials | DSEmu | |||
1-Setup | 4-Sound | 7-FIFO | ||
2-Framebuffer | 5-SRAM | 8-Interrupts | ||
3-Keys | 6-Filesystems | 9-Microphone | ||
10-Extended Rotation Background |
This tutorial was tested and works with libnds dated 2005-10-18. Unfortunately it no longer works with libnds dated 2005-12-12. I'm looking into it.
In tutorial six we came up with a way of having the ARM9 send messages to the ARM7 to notify it to play sounds. While this worked well it has at least two main problems.
The first is that it requires storing data in shared memory with the ARM7 constantly polling that memory to find out if a message has arrived for it. The second is there was no way for the ARM7 to communicate results back to the ARM9. While there are ways of working around some of these problems, a better way exists.
The Nintendo DS has a built in FIFO queue. FIFO stands for 'First In, First Out'. This type of data structure allows a 'sender' to put data on the queue one by one. A 'receiver' can retrieve items from the queue, starting with the first item the sender placed in it (hence the First In First Out name).
Although the FIFO can be polled to see if items exist, it also allows interrupts to be performed when things are placed on or removed from the queue. This is much more efficient than polling. So we could have the ARM9 putting sound commands into the FIFO and the ARM7 would immediately get notified that the queue has an item in it and start playing the sound.
The only documentation I've come across on the FIFO is from reading the DSLinux sources and DSTek. The rest of my knowledge is from experimenting with the registers and trying different things out. We'll start with a simple program that displays the registers, allows placing things in and removing things from the queue, and shows the results on the registers.
There are three memory registers involved in the FIFO:
Define | Address | Size | Description |
---|---|---|---|
REG_IPCFIFOCNT | 0x04000184 | 16 bits | Used for getting the status of the FIFO, reseting it, setting interrupts, etc. |
REG_IPCFIFOSEND | 0x04000188 | 32 bits | Write only register for sending data to the FIFO. |
REG_IPCFIFORECV | 0x04100000 | 32 bits | Read only register for retrieving data from the FIFO. |
The control register has 16 bits of information. The ones that I know about are:
Define | Bit | Read/Write | Description |
---|---|---|---|
IPC_FIFO_SEND_EMPTY | 0 | R | Clear if nothing has been sent, otherwise set. |
IPC_FIFO_SEND_FULL | 1 | R | Set if the send queue is full. |
IPC_FIFO_SEND_IRQ | 2 | R/W | If set an interrupt will occur when something is put on the queue. |
IPC_FIFO_SEND_CLEAR | 3 | W | Empties the send queue when set. |
IPC_FIFO_RECV_EMPTY | 8 | R | Clear if nothing is in the receive queue, otherwise set. |
IPC_FIFO_RECV_FULL | 9 | R | Set if the receive queue is full. |
IPC_FIFO_RECV_IRQ | 10 | R/W | If set an interrupt will occur when something is received from the queue. |
IPC_FIFO_ERROR | 14 | R | Set if an error occurs during a send or receive. |
IPC_FIFO_ENABLE | 15 | R/W | Enables the FIFO queue. |
This is a 32 bit write only register. When a value is written to the register it is placed in the send queue for the processor. This queue can only hold 16 items so the other processor should be receiving these items in a timely manner.
When an item is written to the register bits will change in the control registers on the different CPU's. The value of the control register is different for each CPU. The table below shows the effects on the bits in the control register when the 'sending CPU' writes to the send register:
Name | Effect | |
---|---|---|
Sending CPU | ||
IPC_FIFO_SEND_EMPTY | If cleared it will now be set as the send queue is no longer empty. | |
IPC_FIFO_SEND_FULL | Set if the send queue is full as a result of this send. | |
IPC_FIFO_ERROR | Set if the send queue was already full. | |
Receiving CPU | ||
IPC_FIFO_RECV_EMPTY | If cleared it will now be set as the receive queue is no longer empty. | |
IPC_FIFO_RECV_FULL | Set if the receive queue is full as a result of the send. |
From this you can surmise that the sending processors send queue is the receiving processors receive queue.
This is a 32 bit read only register. When the receive queue is not empty (ie. IPC_FIFO_RECV_EMPTY of the control register is clear) you can read from this register to get the value that the other processor put on it.
When an item is read from the register bits will change in the control registers on the different CPU's. The table below shows the effects on the bits in the control register when the 'receiving CPU' reads from the receive register:
Name | Effect | |
---|---|---|
Receiving CPU | ||
IPC_FIFO_RECV_EMPTY | This is cleared if the item received was the last item in the receive queue. | |
IPC_FIFO_RECV_FULL | Cleared if the queue was full before the receive was performed. Receiving the last item means the queue is no longer full. | |
IPC_FIFO_ERROR | Set if an attempt was made to receive from an empty receive queue. | |
Receiving CPU | ||
IPC_FIFO_SEND_EMPTY | This is cleared if there are no more items in the send queue for this processor. | |
IPC_FIFO_SEND_FULL | Cleared if the queue was full before the receive was performed. Receiving the last item means the queue is no longer full. |
From the register explanation it should be a bit clearer how the FIFO works. Each CPU has a queue which it can put data on. This is done by writing any 32-bit value to REG_IPCFIFOSEND. The other CPU can receive this data by reading from REG_IPCFIFORECV. It will get the oldest item that the original CPU has put on the queue.
The bits in the control register, REG_IPCFIFOCNT, can be used to detect if there are items to be read and if the queue is empty or full.
Up to 16 items can be placed in the queue before it is full. This is 16 items in each send queue. So the ARM9 can have 16 items that it has sent to the ARM7 and the ARM7 can have 16 items sent to the ARM9 at the same time. Although the same register numbers are used, each CPU has it's own queue allowing bidirectional communication.
To retrieve data from the queue using polling it's probably best to check to see if the receive queue is empty, and if it is not then process an item from it. Or loop, processing all items. Something like:
// One item if(!(REG_IPCFIFOCNT & IPC_FIFO_RECV_EMPTY)) processItem(REG_IPCFIFORECV); // All items while(!(REG_IPCFIFOCNT & IPC_FIFO_RECV_EMPTY)) processItem(REG_IPCFIFORECV);
The loop approach has the possibility of never ending if the sending processor is also in a loop adding items to the queue. You could also use interrupts to be notified when the receiving queue has data in it. This is a topic for a later tutorial.
The example application I used to test this out is called fifo_demo1. It uses simple console output to display the contents of the registers and with button presses allows the ARM7 and ARM9 to send and receive data. The effect on the registers is displayed.
When the users presses 'Up', the FIFO register for a particular CPU is enabled. When 'Right' is pressed, an item of data is written to the send register. I use a simple incrementing number. When 'Down' is pressed an item is read from the receive register and stored in a variable for display during the VBlank interrupt.
The CPU that does the operation is controlled by the left and right shoulder keys. Holding the left shoulder key down while pressing the directional buttons results in the operations occurring on the ARM7. Holding the right shoulder button down uses the ARM9.
Shoulder Button | Direction Button | Action |
---|---|---|
Left | Up | Enable ARM7 FIFO |
Left | Right | Write on the ARM7's send queue |
Left | Down | Receive from the ARM7's receive queue |
Right | Up | Enable ARM9 FIFO |
Right | Right | Write on the ARM9's send queue |
Right | Down | Receive from the ARM9's receive queue |
A simple test of operation is:
ARM9 Fifo Registers: REG_IPCFIFOCNT: 101 REG_IPCFIFORECV: ARM7 Fifo Registers: REG_IPCFIFOCNT: 101 REG_IPCFIFORECV:The numbers are in hexadecimal. This shows that the FIFO's start off disabled with the send and receive queue's empty (0x0101 is 0b100000001. Bits 0 and 8 are set for send and receive empty respectively).
if((keysHeld() & KEY_R) && (keysDown() & KEY_UP)) {
REG_IPCFIFOCNT = IPC_FIFO_ENABLE | IPC_FIFO_SEND_CLEAR;
}
The screen updates to show that bit 15 of the control register for the
ARM9 has been set, making it enabled. The ARM7 value does not change:
ARM9 Fifo Registers:
REG_IPCFIFOCNT: 8101
REG_IPCFIFORECV:
ARM7 Fifo Registers:
REG_IPCFIFOCNT: 101
REG_IPCFIFORECV:
if((keysHeld() & KEY_R) && (keysDown() & KEY_RIGHT)) {
static int count = 0;
REG_IPCFIFOSEND = ++count;
}
The change in the registers is shown on the screen. The value of the
control register on the ARM9 changes to 0x8100. the only change is
that bit 0 is now clear, meaning the send queue is no longer
empty. The value of the ARM7 control register is 0x0001. Bit 8 is now
clear meaning its receive queue is no longer empty. Bit 1 is still set
showing that the ARM7's send queue is empty. Remember there are two
queues, one for each direction (ARM7->ARM9 and ARM9->ARM7). We just
put something on ARM9's send queue so that shows as not empty whereas
we haven't touched ARM7's so it is still showing empty.
ARM9 Fifo Registers: REG_IPCFIFOCNT: 8100 REG_IPCFIFORECV: ARM7 Fifo Registers: REG_IPCFIFOCNT: 1 REG_IPCFIFORECV:
ARM9 Fifo Registers:
REG_IPCFIFOCNT: 8100
REG_IPCFIFORECV:
ARM7 Fifo Registers:
REG_IPCFIFOCNT: 8001
REG_IPCFIFORECV:
arm7_fifo->recv = REG_IPCFIFORECV;The control register on the ARM9 changes back to 8101. The send queue has changed back to empty since we've received the value. On the ARM7 the value is also 8101. Our receive queue is empty since we've received the only value on it. The REG_IPCFIFORCV field on the screen for the ARM7 seven shows '1' which is the value the ARM9 sent.
ARM9 Fifo Registers: REG_IPCFIFOCNT: 8101 REG_IPCFIFORECV: ARM7 Fifo Registers: REG_IPCFIFOCNT: 8101 REG_IPCFIFORECV: 1
ARM9 Fifo Registers: REG_IPCFIFOCNT: 8102 REG_IPCFIFORECV: ARM7 Fifo Registers: REG_IPCFIFOCNT: 8201 REG_IPCFIFORECV: 1
As libnds has not yet got defines for the full range of operations on the FIFO I created a fifo.h file that contains appropriate defines:
#define REG_IPCFIFOCNT (*(vu16*)0x4000184) #define REG_IPCFIFOSEND (*(vu32*)0x4000188) #define REG_IPCFIFORECV (*(vu32*)0x4100000) #define IPC_FIFO_SEND_EMPTY (1<<0) #define IPC_FIFO_SEND_FULL (1<<1) #define IPC_FIFO_SEND_IRQ (1<<2) #define IPC_FIFO_SEND_CLEAR (1<<3) #define IPC_FIFO_RECV_EMPTY (1<<8) #define IPC_FIFO_RECV_FULL (1<<9) #define IPC_FIFO_RECV_IRQ (1<<10) #define IPC_FIFO_ERROR (1<<14) #define IPC_FIFO_ENABLE (1<<15)
The demo program displays the contents of the control registers for the two CPU's:
consolePrintf("ARM9 Fifo Registers:\n"); consolePrintf("REG_IPCFIFOCNT: %x\n", REG_IPCFIFOCNT); consolePrintf("REG_IPCFIFORCV: %x\n", arm9_recv); consolePrintf("ARM7 Fifo Registers:\n"); consolePrintf("REG_IPCFIFOCNT: %x\n", arm7_fifo->cnt); consolePrintf("REG_IPCFIFORCV: %x\n", arm7_fifo->recv);
The value of the 'recv' register is stored in a variable when it is read on an appropriate button press. As reading this register destructively modifies the queue (it removes the data from it) I can't read the actual register every VBlank.
The code to handle the key presses on the ARM9 side is pretty basic:
scanKeys(); // ARM9 Keys if((keysHeld() & KEY_R) && (keysDown() & KEY_UP)) { REG_IPCFIFOCNT = IPC_FIFO_ENABLE | IPC_FIFO_SEND_CLEAR; } if((keysHeld() & KEY_R) && (keysDown() & KEY_RIGHT)) { static int count = 0; REG_IPCFIFOSEND = ++count; } if((keysHeld() & KEY_R) && (keysDown() & KEY_DOWN)) { arm9_recv = REG_IPCFIFORECV; }
On the reading and writing of the registers on the ARM7 we need to handle the keypress on the ARM9 and send a message to the ARM7 telling it to store the value of the registers or read a value. The FIFO would be ideal for this but since we want to display it's values I use the same 'control' type structure used in tutorial six, except instead of playing sounds we read and write registers.
The code for this is held in command.h, command7.cpp and command9.cpp. The ARM7 commands are:
static void CommandFIFOInit() { REG_IPCFIFOCNT = IPC_FIFO_ENABLE | IPC_FIFO_SEND_CLEAR; } static void CommandFIFOSend() { static int count = 0; REG_IPCFIFOSEND = ++count; } static void CommandFIFORecv() { arm7_fifo->recv = REG_IPCFIFORECV; } void CommandProcessCommands() { static int currentCommand = -1; while(currentCommand != commandControl->currentCommand) { Command* command = &commandControl->command[currentCommand]; switch(command->commandType) { case FIFO_INIT: CommandFIFOInit(); break; case FIFO_SEND: CommandFIFOSend(); break; case FIFO_RECV: CommandFIFORecv(); break; } currentCommand++; currentCommand &= MAX_COMMANDS-1; } }
And they are sent on keypresses from the ARM9:
void CommandFIFOInit() { Command* command = &commandControl->command[commandControl->currentCommand]; command->commandType = FIFO_INIT; commandControl->currentCommand++; commandControl->currentCommand &= MAX_COMMANDS-1; } void CommandFIFOSend() { Command* command = &commandControl->command[commandControl->currentCommand]; command->commandType = FIFO_SEND; commandControl->currentCommand++; commandControl->currentCommand &= MAX_COMMANDS-1; } void CommandFIFORecv() { Command* command = &commandControl->command[commandControl->currentCommand]; command->commandType = FIFO_RECV; commandControl->currentCommand++; commandControl->currentCommand &= MAX_COMMANDS-1; } int main() { [...] // ARM7 Keys if((keysHeld() & KEY_L) && (keysDown() & KEY_UP)) { CommandFIFOInit(); } if((keysHeld() & KEY_L) && (keysDown() & KEY_RIGHT)) { CommandFIFOSend(); } if((keysHeld() & KEY_L) && (keysDown() & KEY_DOWN)) { CommandFIFORecv(); } [...] }
You'll notice the ARM7 code sets member variables of an arm7_fifo structure. This is a structure held in shared memory so that the ARM9 can access it. The code to define this is in transfer.h:
/* Quick and dirty code to allow the ARM7 to inform the ARM9 of the current values of the FIFO registers */ struct ARM7_FIFO { uint32 cnt; uint32 send; uint32 recv; }; #define arm7_fifo ((ARM7_FIFO*)((uint32)(IPC)+sizeof(TransferRegion)+sizeof(CommandControl)))
The location of this shared structure is placed after the libnds TransferRegion and the CommandControl stucture I use to send commands to the ARM7.
One good thing about using the FIFO for this sort of thing in the future is we won't need to play around with shared memory offsets to do this sort of thing.
The complete example program is 'fifo_demo1'. The ARM9 code is in arm9_main.cpp. It uses the console routines to print information similar to the first tutorial. The ARM7 code is in arm7_main.cpp. A Makefile file is supplied to build everything.
The complete source code is supplied in fifo_demo1.zip and you can download the fifo_demo1.nds and fifo_demo1.nds.gba files for running on the emulators or hardware.
This tutorial covered how to use the FIFO registers which I believe are new to the Nintendo DS. It presented a demo program that allowed sending and receiving data and showing the results on the registers involved.
Remember that this demo only used integers as queue items. You can use pointers to anything you want. Integers, characters, or pointers to arbitary structures. Ideally you'd probably want to come up with a protocol and use this structure to pass the protocol information around.
There is the ability to set interrupts to occur when the send queue is empty or the recv queue is not empty. This removes the need to poll the control register to check the status of the queue. This tutorial has already gotten quite long so a future tutorial will demonstrate how to do this as well as how to use the FIFO in a more 'real world' situation.
As always, any comments or suggestions are welcomed. See my contact details below.
Copyright (c) 2005, Chris Double. All Rights Reserved.