目录
1.FIFO (First Input First Output) 一种先进先出的数据缓存器,先进入的数据先从FIFO缓存器中读出,与RAM相比没有外部读写地址线,使用比较简单,但只能顺序写入数据,顺序的读出数据,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
2.FIFO一般用于不同时钟域之间的数据传输,比如FIFO的一端是AD数据采集,另一端为PCI总线,那么在两个不同的时钟域间就可以采用FIFO来作为数据缓冲。另外对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。
3.可以将FIFO分为同步FIFO和异步FIFO。同步FIFO是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作;异步FIFO是指读写时钟不一致,读写时钟是互相独立的。对于异步FIFO一般有两种理解,一种是读写操作不使用时钟,而是直接采用wr_en(Write Enabled)和rd_en(Read Enabled)来进行控制;另一种,是指在FPGA和ASIC设计中,异步FIFO具有两个时钟的双口FIFO, 读些操作在各自的时钟延上进行,在两个不同时钟下,可以同时进行读或写。异步FIFO在FPGA设计汇总占用的资源比同步FIFO大很多,所以尽量采用同步FIFO设计。然而对于ARM 系统内绝大部分外设接口都是异步 FIFO。
4.FIFO数据缓存器设计的难点在于怎样判断FIFO的空/满状态。
假设一个深度为128字节的FIFO怎样工作(使用已转换为二进制的指针)。FIFO_WIDTH=8,FIFO_DEPTH= 2^N = 128,N = 7,指针宽度为N+1=8。起初rd_ptr_bin和wr_ptr_bin均为“00000000”。此时FIFO中写入128个字节的数据。wr_ptr_bin =“10000000”,rd_ptr_bin=“00000000”。当然,这就是满条件。现在,假设执行了8次的读操作,使得rd_ptr_bin =“10000000”,这就是空条件。
本文用数据结构的方式来完成FIFO缓冲。
1.缓存区的制作
思路:按照输入数据的顺序输出数据
简单地数据存储:
struct FIFO_BUF{
unsigned char data[128];
int next;
}fifo_buf;
unsigned char buffer[128]={0};//用buffer模拟输入数据
void init_write_to_buf(void)
{
if(fifo_buf.next<128)
{
fifo_buf.data[fifo_buf.next]=buffer[fifo_buf.next]; //将buffer中的数据写入到fifo_buf中;
fifo_buf.next++;
}
return 0;
}
fifo_buf.next的起始点是0,所以最初存储的数据是fifo_buf.data[0],下一个是fifo_buf.data[1],依次类推,一共128个存储位置。
下一个存储位置有用变量next管理,这样就可以记住128数据而不溢出,为保险next变为128(即127已经写了)之后就不要了。
简单地数据读取:
void read_from_fifo_buf(void)
{
for(;;)
{
if(fifo_buf.next==0)
return; //为0表示没有数据,不用读取
else
{
i = fifo_buf.data[0]; //读取第0个位置的元素;
fifo_buf.next--;
for(int j=0;j<fifo_buf.next;j++)//将后面的数据往前移
{
fifo_buf.data[j] = fifo_buf.data[j+1];
}
}
}
}
如果next不是0,说明至少有一个数据,最开始的一个数据肯定放在data[0]中,将这个数据放入到变量i中,这样数据就减少了一个,所以next要减去1;
接下来的for语句工作原理如下:
数据存放的位置全部都向前移动了一个位置。如果不移送的话,下次就不能从data[0]读入数据了。
此方法的缺点:移送数据的处理,一般不会超过3个,且基本上没问题。有时需要移送上百个数据,就会出现问题。
2 改善上述缓存区
思路:维护下一个要写入数据的位置和下一个要读出数据的位置。就像数据读出位置追着数据写入位置跑一样。这样就不需要数据移送操作了。数据读出位置追上数据写入位置的时候,就相当于缓存区为空,没有数据。
但是这样的缓存区使用一段时间后,下一个数据写入位置会变为31,而这时下一个数据读出位置可能已经是29或者30了。若干次后,当下一个写入位置变为128的时候,就没地方可以写入数据了。
如果当下一个数据写入位置到达缓存区终点时,数据读出位置也恰好到达缓冲区终点,也就是缓冲区正好变为空,这种情况我们只要将下一个数据写入位置和下一个数据读出位置都再置位0就行了,就像转回去从头再来一样。
但是总还是会有数据读出位置没有追上数据写入位置的情况。这时又不得不进行数据移送操作。
3 缓存区的实现
当下一个数据写入位置到达缓冲区最末尾时,缓存区开头部分应该已经变空了(如果还没有变空,说明数据读出跟不上数据写入,只能把部分数据扔掉了)。因此如果下一个数据写入位置到了128以后,就强制性的将它设置为0。这样写一个数据写入位置就跑到了下一个数据读出位置的后面。
对下一个数据读出位置也做同样的处理。一旦到了128以后,就把它设置为从0开始继续读取数据。这样128字节的缓存区就能一圈一圈地不停循环,长久使用。数据移送操作一次都不需要。这个缓存区虽然只有128字节,只要不溢出的话,他就能持续使用下去。
数据的写入
struct FIFO_BUF{
unsigned char data[128];
int next_w; //写位置标志
int next_r; //读位置标志
int len; //数据长度
}fifo_buf;
void write_to_fifo_buf(void)
{
if(fifo_buf.len>=128)
return;
else
{
fifo_buf.data = data; //将一个数据写入到缓存中
fifo_buf.next_w++;
fifo_buf.len++;
if(fifo_buf.next_w==128)
fifo_buf.next_w=0;
}
return 0;
}
数据的读取
void read_from_fifo_buf(void)
{
for(;;)
{
i = fifo_buf.data[fifo_buf.next_r]; //读取next_r处的数据
fifo_buf.len--;
fifo_buf.next_r++;
if(fifo_buf.next_r==128)
{
fifo_buf.next_r=0;
}
}
}
4 FIFO缓存区最终实现
struct FIFO_BUF
{
unsigned char *buf;
int fifo_w;
int fifo_r;
int len; //缓存区总的字节数
int cache_data; //用于保存缓存区里没有数据的字节数
int flags;
}fifo_buf;
初始化
void fifo_init(fifo_buf *fifo,int len,unsigned char *buf)
{
fifo->buf = buf;
fifo->len = len;
fifo->cache_data = len;
fifo->fifo_w = 0;
fifo->fifo_r = 0;
fifo->flags = 0;
return;
}
写
#define FLAGS_OVERRUN 0x00000001
int fifo_write(struct fifo_buf *fifo,unsigned char data)
{
if(fifo->cache_data == 0) //空余没有了,溢出
{
fifo->flags |= FLAGS_OVERRUN;
return -1;
}
fifo->buf[fifo->fifo_w]=data;
fifo->fifo_w++;
if(fifo->fifo_w==fifo->len)
{
fifo->fifo_w = 0;
}
fifo->cache_data--;
return 0;
}
读
int fifo_read(fifo_buf *fifo,unsigned char buffer)
{
if(fifo->cache_data == fifo->len)
{
return -1;
}
else
{
buffer = fifo->buf[fifo->fifo_r]; //一个数据写入到buffer中
fifo->fifo_r++;
if(fifo->fifo_r==32)
{
fifo->fifo_r = 0;
}
fifo->cache_data++;
}
return buffer;
}
查询写入了多少数据
int fifo_status(void)
{
return fifo->len - fifo->cache_data;
}