掌叔
2008-06-15 09:37:51
摘自:ndsbbs
作者:nashi1987
FIFO:
在教程6中我们讲解了一种处理器间通信的方法,虽然能很好的工作但有两个问题。第一是他需要将数据存储到一个共享内存中,ARM7不断检测这部分内存以判断是否有新信息写入。第二是这样没有办法让ARM7返回数据给 ARM9。有一种新方法可以解决这些问题。DS有一个内置的FIFO队列。FIFO是基于‘先入先出’。这种数据结构允许一个‘发送者’将将数据一个一个的顺序存入队列,一个‘接受者’可以从队列中得到数据,从发送者先存入的开始。FIFO由此得名。FIFO也有一个中断,它在有数据传入或移出队列时发生。这样就比不断检测更有效。所以我们可以让ARM9在将指令写入队列时ARM7可以立即知道在队列中有新项目并立即执行。
本教程借鉴了DSLINUX和DSTEK的相关资料。
FIFO寄存器:
与FIFO有关的寄存器有3个,他们是:
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.
REG_IPCFIFOCNT:
控制寄存器有16位,我所知道的是:
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.
REG_IPCFIFOSEND:
这是一个32位只写寄存器。当一个数据写入这个寄存器时它被放到处理器的传送队列中。这个队列只可以存储16项,所以另一个处理器应当及时的接受这些项目。当一个项目写入时,不同处理器的控制寄存器的一些位会改变。不同的CPU控制寄存器的值不同。下面的表格显示当一个‘传送CPU’向传送寄存器写入数据后对控制寄存器中一些位的影响:
Name Effect
Sending CPU
IPC_FIFO_SEND_EMPTY 传送队列不为空时置位
IPC_FIFO_SEND_FULL 传送时如果传送队列已满则置位
IPC_FIFO_ERROR 如果传送队列已满则置位
Receiving CPU
IPC_FIFO_RECV_EMPTY 接收队列不为空时置位
IPC_FIFO_RECV_FULL 传送时如果接收队列已满则置位
从以上数据你可以猜到传送CPU的传送队列是接收CPU的接收队列。
REG_IPCFIFORECV:
这是一个32位只读寄存器。当接收队列不为空时,你可以从中读取另一个寄存器存如的数据。
当一个项目从寄存器中读取后另一个CPU的控制寄存器的一些位会改变。下面的表格描述当接收CPU从这个寄存器中读取一个项目后对控制寄存器的影响:
Name Effect
Receiving CPU
IPC_FIFO_RECV_EMPTY 当读取的项目是接收队列的最后一项时清零
IPC_FIFO_RECV_FULL 接收执行前若队列已满则清零
IPC_FIFO_ERROR 当尝试从空队列中读取项目时置位
Receiving CPU
IPC_FIFO_SEND_EMPTY 当传送队列为空时清零
IPC_FIFO_SEND_FULL 接收执行前若队列已满则清零
FIFO用法:
通过寄存器的解释我们应该对FIFO如何工作了解一些了。每一个CPU有一个可以存放数据的队列。数据写入通过将32为数据写入 ‘REG_IPCFIFOSEND’寄存器完成。另一个CPU可以通过从‘REG_IPCFIFORECV’寄存器中读取数据来接收这些数据。它将接收到原始CPU最先放入的数据。在控制寄存器‘REG_IPCFIFOCNT’中的一些位可用来判断队列中是否有需要读取的项目以及队列为空或已满。最多可以向队列中存储16个项目。所以同时ARM9和ARM7间可以相互传输16个项目。虽然使用相同的寄存器数量,但每一个CPU有独立的队列来实现双向通信。使用轮流检测来从队列中接收数据,最好先检测队列是否为空,若不为空则处理一个项目,或循环处理所有项目。例如:
// One item
if(!(REG_IPCFIFOCNT & IPC_FIFO_RECV_EMPTY))
processItem(REG_IPCFIFORECV);
// All items
while(!(REG_IPCFIFOCNT & IPC_FIFO_RECV_EMPTY))
processItem(REG_IPCFIFORECV);
如果传送CPU也在添加项目循环中则这个循环很可能不能终止。你也可以使用中断来判断接收队列中是否有新项目。这在以后的教程中会讲解。
示例程序:
我们用来测试这些的示例程序叫做fifo_demo1。它使用简单的控制行输出来显示寄存器中的内容,当按键按下后ARM7与ARM9间相互传输数据,对寄存器产生的效果被显示出来。当使用者按下“上”时,相关CPU的FIFO寄存器启动。当“右”按下时,向传送寄存器写入一个项目。当“下”按下后从接收寄存器中读取一个项目储存到一个变量中并在垂直扫描中断时显示。所执行操作的CPU由左右肩的按键(R/L)控制。按住L键不放则操作按键的操作由ARM7 执行,按住R键不放则使用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
一个简单的测试操作:
1.初始化时屏幕显示:
ARM9 Fifo Registers:
REG_IPCFIFOCNT: 101
REG_IPCFIFORECV:
ARM7 Fifo Registers:
REG_IPCFIFOCNT: 101
REG_IPCFIFORECV:
数字是十六进制的,这显示FIFO因为接收和传送队列为空而禁用。
2.使用以下代码,在R键按住并按下‘上’键时激活ARM9的FIFO。:
if((keysHeld() & KEY_R) && (keysDown() & KEY_UP)) {
REG_IPCFIFOCNT = IPC_FIFO_ENABLE | IPC_FIFO_SEND_CLEAR;
}
屏幕更新后显示控制寄存器的第15位置位,激活FIFO,ARM7的值不改变:
ARM9 Fifo Registers:
REG_IPCFIFOCNT: 8101
REG_IPCFIFORECV:
ARM7 Fifo Registers:
REG_IPCFIFOCNT: 101
REG_IPCFIFORECV:
3.下一步按住R键不放并按下方向右键,使ARM9向REG_IPCFIFOSEND寄存器写入数据,记数变量增加:
if((keysHeld() & KEY_R) && (keysDown() & KEY_RIGHT)) {
static int count = 0;
REG_IPCFIFOSEND = ++count;
}
寄存器的改变显示在屏幕上,ARM9的控制寄存器的值改变为0x8100。唯一的改变是0位清零,表示传送队列不再为空。ARM7控制寄存器的值为 0x0001,第8位清零表示其接收队列不为空。我们只是放一些东西到ARM9的传送队列令其不为空,但还未对ARM7进行操作,所以它依然为空。
ARM9 Fifo Registers:
REG_IPCFIFOCNT: 8100
REG_IPCFIFORECV:
ARM7 Fifo Registers:
REG_IPCFIFOCNT: 1
REG_IPCFIFORECV:
4.让我们从ARM7端接收数据吧。首先我们需要激活ARM7的FIFO。按住L键并按下方向上键。使用于先前讲的ARM9的代码相似的代码写ARM7的REG_IPCFIFOCNT寄存器。寄存器的改变显示ARM7的控制寄存器的第15位置位,表示已激活:
ARM9 Fifo Registers:
REG_IPCFIFOCNT: 8100
REG_IPCFIFORECV:
ARM7 Fifo Registers:
REG_IPCFIFOCNT: 8001
REG_IPCFIFORECV:
5.按住L键并按下方向下键,这使ARM7读取REG_IPCFIFORECV的值并存储到一个ARM9可以显示的变量中。
arm7_fifo->recv = REG_IPCFIFORECV;
ARM9的控制寄存器的值变回8101,我们接收数据之后传送队列变为空状态。ARM7的值依然是8101。屏幕上显示ARM7的REG_IPCFIFORCV的值为ARM9传送的“1”。
ARM9 Fifo Registers:
REG_IPCFIFOCNT: 8101
REG_IPCFIFORECV:
ARM7 Fifo Registers:
REG_IPCFIFOCNT: 8101
REG_IPCFIFORECV: 1
6. 尝试按住R键并按下方向右键16次,这导致对ARM9的传送队列的16次写操作-队列所能容纳的最大值。在第16次时ARM9的控制寄存器的值变为 0x8102。IPC_FIFO_SEND_FULL置位显示传送队列已满。ARM7的值为0x8201,表示IPC_FIFO_RECV_FULL 位置位,表示是接收队列已满。
ARM9 Fifo Registers:
REG_IPCFIFOCNT: 8102
REG_IPCFIFORECV:
ARM7 Fifo Registers:
REG_IPCFIFOCNT: 8201
REG_IPCFIFORECV: 1
7. 按住L键并按下方向下键16次来完全清空队列。每按一次下应当以FIFO格式显示从ARM9传送的数字。所以应当是2,3, 4, 5, 等等。第一次会立即改变ARM9的控制寄存器为 0x8100 以及ARM7的变为0x8001因为队列满指示位清零。在第16次队列空指示位置位。
8.尝试按住R键并按下方向左键16次来填充ARM9的队列并同样的填满ARM7的队列。之后在按方向下键前交替的使用R,L键来接收数据,一次一个,在ARM7和ARM9间交替。这表明每一个处理器的队列是独立的并且可以容纳16个项目。
9.在队列为空时试着接收数据或向一个已满的队列传输项目,将会导致IPC_FIFO_ERROR位置位。
执行:
因为LIBNDS没有定义所有的与FIFO相关的东西,我创建了一个包含相关定义的fifo.h文件:
#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)
演示程序将显示两个CPU的控制寄存器的内容:
consolePrintf("ARM9 Fifo Registers:
");
consolePrintf("REG_IPCFIFOCNT: %x
", REG_IPCFIFOCNT);
consolePrintf("REG_IPCFIFORCV: %x
", arm9_recv);
consolePrintf("ARM7 Fifo Registers:
");
consolePrintf("REG_IPCFIFOCNT: %x
", arm7_fifo->cnt);
consolePrintf("REG_IPCFIFORCV: %x
", arm7_fifo->recv);
'recv' ,寄存器的值储存在一个变量中,在指定按键按下时读取。因为我不能破坏性的读取寄存器(对寄存器的读取会将数据移出)。由ARM9处理按键的代码:
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;
}
对ARM7 的寄存器的读写,我们需要用ARM9处理按键操作并告诉ARM7将寄存器的值储存或读取一个值。对这个FIFO会是个不错的主意,但我们这里使用教程6中使用的 'control' 结构,我们使用读写寄存器操作替代播放声音操作。这些代码包含在command.h, command7.cpp 和 command9.cpp中。ARM7代码如下:
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;
}
}
它们在按键按下时由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();
}
[...]
}
你注意到了ARM7的代码中设置了一个‘an arm7_fifo 结构’成员变量,这是一个用来在共享内存中的数据,为了方便存取。代码定义在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)))
以上就是FIFO详细讲解,在程序设计中FIFO会带来很多方便。
如有疏漏错误之处,还望大家多多指正!