Aus RN-Wissen.de
Version vom 13. Februar 2006, 19:12 Uhr von SprinterSB_alt (Diskussion | Beiträge)

(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche


Eine FIFO (von engl. first in, first out) ist eine Warteschlange. Sie funktioniert nach dem Prinzip "wer zuerst kommt, mahlt zuerst" und unterscheidet sich dadurch von einem Stapel (Stack, Kellerspeicher).

Verwendung

FIFOs finden dort Verwendung, wo Daten zwischengespeichert werden müssen. Typischerweise werden die Daten von einem Teilnehmer/Programmteil produziert und von einem anderen Teil konsumiert bzw. Verbraucht – man hat also ein Producer-Consumer-Paradigma.

Häufiges Beispiel ist eine Kommunikation, bei der Daten schneller gesendet werden, als der Empfänger sie verarbeiten kann. Damit keine Daten verloren gehen, werden sie beim Empfang in eine FIFO gespeichert und warten dort auf ihre Verarbeitung.

Auch in Ausgaberichtung werden die Daten oft nur in einen Puffer (FIFO) geschrieben. Während die Ausgaberoutine, Hardware bzw Task/Prozess die Daten verschickt, läuft das eigentliche Programm schon weiter und muss nicht warten, bis die Ausgabe fertig ist.

C-Code

Die Implementierung geht davon aus, daß es mehrere Producer und Consumer gibt. Producer bzw. Consumer mussen sich jeweils auf der gleichen Interrupt-Ebene befinden, aber die Producer können natürlich auf einer anderen Ebene arbeiten als die Consumer.

Sind die Producer (gleiches gilt für die Consumer) auf mehrere Interrupt-Ebenen verteil, müssen die entsprechenden Funktionen atomar gemacht werden! (Und nicht nur der Zugriff auf die Komponente count.)

Interface

void fifo_init (fifo_t *b, uint8_t *buffer, const uint8_t size)
Initialisiert die FIFO, setzt Lese- und Schreibzeiger, etc.
uint8_t fifo_put (fifo_t *b, const uint8_t data)
Schreibt ein Byte in die FIFO. Liefert 1 bei Erfolg und 0, falls die FIFO voll ist.
uint8_t fifo_get_wait (fifo_t *b)
Liefert das nächste Byte aus der FIFO, bei leerer FIFO wird gewartet, bis das nächste Zeichen eintrifft.
int fifo_get_nowait (fifo_t *b)
Liefert das nächste Byte aus der FIFO als int bzw. -1, falls die FIFO leer ist.

Die eigentliche Implementierung der FIFO-Funktionalitäten put und get erfolgen als Inline-Funktionen, um bei zeitkritischen Teilen schneller zu sein.

static inline uint8_t _inline_fifo_put (fifo_t *b, const uint8_t data)
Wie fifo_put
static inline uint8_t _inline_fifo_get (fifo_t *b)
Liefert das nächste Zeichen aus der FIFO. Ob überhaupt ein Zeichen in der FIFO ist, muss vorher extra abgeprüft werden.

Header

fifo.h
Hier wird die FIFO-Typ fifo_t definiert, die Basis-Funktionalitäten put und get werden als Inline-Funktionen implementiert.
#ifndef _FIFO_H_
#define _FIFO_H_

#include <avr/io.h>
#include <avr/interrupt.h>

extern void fifo_init (fifo_t*, uint8_t* buf, const uint8_t size);
extern uint8_t fifo_put (fifo_t*, const uint8_t data);
extern uint8_t fifo_get_wait (fifo_t*);
extern int fifo_get_nowait (fifo_t*);

typedef struct
{
	volatile uint8_t count;
	uint8_t size;
	uint8_t *pread;
	uint8_t *pwrite;
	uint8_t *buf_end;
	uint8_t *buf;
} fifo_t;

static inline uint8_t
_inline_fifo_put (fifo_t *b, const uint8_t data)
{
	if (b->count >= b->size)
		return 0;
		
	uint8_t *pwrite = b->pwrite;
	
	*(pwrite++) = data;
	
	if (pwrite == b->buf_end)
		pwrite = b->buf;
	
	b->pwrite = pwrite;
	
	uint8_t sreg = SREG;
	cli();
	b->count++;
	SREG = sreg;
	
	return 1;
}

static inline uint8_t 
_inline_fifo_get (fifo_t *b)
{
	uint8_t *pread = b->pread;
	uint8_t data = *(pread++);
	
	if (pread == b->buf_end)
		pread = b->buf;
	
	b->pread = pread;
	
	uint8_t sreg = SREG;
	cli();
	b->count--;
	SREG = sreg;
	
	return data;
}

#endif /* _FIFO_H_ */

Quellcode

fifo.c
Hier findet sich die Implementierung von fifo_init. fifo_put, fifo_get_wait und fifo_get_wait sind lediglich Wrapper der Inline-Funktionen aus dem Header. Dadurch kann in weniger zeitkritischen Fällen der Codeverbrauch vermindert werden.
#include <avr/io.h>
#include <avr/interrupt.h>

#include "fifo.h"

void fifo_init (fifo_t *b, uint8_t *buffer, const uint8_t size)
{
	b->buf = buffer;
	b->count = 0;
	b->pwrite = buffer;
	b->pread =  buffer;
	b->buf_end = buffer + size;
	b->size = size;
}

uint8_t fifo_put (fifo_t *b, const uint8_t data)
{
	return _inline_fifo_put (b, data);
}

uint8_t fifo_get_wait (fifo_t *b)
{
	while (!b->count);
	
	return _inline_fifo_get (b);	
}

int fifo_get_nowait (fifo_t *b)
{
	if (!b->count)		return -1;
		
	return (int) _inline_fifo_get (b);	
}

Anwendung

Initialisierung

Bevor eine FIFO benutzt werden kann, muss sie initialisiert werden. Dazu definiert man sich einen Puffer und übergibt diesen samt Größe an </fifo_init:

#include "fifo.h"

#define BUF_SIZE 10

uint8_t buffer[BUF_SIZE];

fifo_t fifo;

...
    fifo_init (&fifo, buffer, BUF_SIZE);       
...

Alternativ lässt man den Startup-Code die Initialisierung übernehmen und schreibt sie explizit in einem Initialtzer hin. In diesem Falle müssen die Werte aber zur Compile-Zeit bekannt sein.

#include "fifo.h"

#define BUF_SIZE 10

uint8_t buffer[BUF_SIZE];

fifo_t fifo = 
{
	.size = BUF_SIZE,
	.pread   = buffer,
	.pwrite  = buffer,
	.buf     = buffer,
	.buf_end = buffer + BUF_SIZE,
    	.count = 0
};

Ein Byte lesen (mit warten) bzw. schreiben:

    uint8_t zeichen = ...;
    fifo_put (&fifo, zeichen);

    zeichen = fifo_get_wait (&fifo);

Siehe auch