\documentclass[11pt,a4paper]{article}
\usepackage[ansinew]{inputenc}
\usepackage[margin=2.5cm,nohead]{geometry}
\usepackage{amssymb}
\usepackage{amsmath}
\usepackage{wasysym}
\usepackage{ngerman,latexsym,alltt,graphicx,textcomp}
\usepackage[bookmarks, colorlinks=false, pdftitle={MRT II Praktikumsprotokoll},
pdfauthor={Fabian Kurz, http://fkurz.net/}, pdfsubject={DSP}, pdfkeywords={TU Dresden, Praktikum, Mikrorechentechnik}, linkbordercolor={1 1 1}]{hyperref}
\date{02. Mai 2005}
\author{Gruppe 50:\\Marcel~Junige, Fabian~Kurz\\Martin~Laabs, Lars Lindenmüller}
\title{Praktikumsprotokoll Mikrorechentechnik II --\\
"`Grundlagen der digitalen Signalverarbeitung"'}
\begin{document}
\setcounter{secnumdepth}{4}
\maketitle
\tableofcontents
\newpage
\section{Aufgabenstellung}
Aufgabe des Praktikums war die Implementierung eines Funktionsgenerators und
eines nichtrekursiven FIR Filters auf der Basis eines ADSP-21020 DSP Chips.

\subsection{Aufgabe 1 - Funktionsgenerator}
Der zu implementierende Funktionsgenerator soll an den beiden Ausg\"angen
\texttt{OUT A} und \texttt{OUT B} der AD7769 D/A-Wandler je nach Betriebsart
entweder ein Sinus- und ein Rechtecksignal oder eine Rampenfunktion und ein
Dreieckimpuls mit einer vorgegebenen Periodendauer $T$ und Amplitude
$max$ erzeugen, wie in 
Abbildung \ref{funktionen} dargestellt.

\begin{figure}[h]
\center
\begin{picture}(150,210)
\put(0,110){
\put(20,0){\vector(0,1){95}}
\put(15,40){\vector(1,0){130}}
\multiput(19,10)(0,60){2}{\line(1,0){2}}
\put(17,10){\makebox(0,0)[r]{$\scriptstyle -max$}}
\put(17,70){\makebox(0,0)[r]{$\scriptstyle max$}}
\thicklines
\qbezier(20,40)(32.5,70)(45,70)
\qbezier(45,70)(57.5,70)(70,40)
\qbezier(70,40)(82.5,10)(95,10)
\qbezier(95,10)(107.5,10)(120,40)
\thinlines
\multiput(70,38)(50,0){2}{\line(0,1){4}}
\put(69,35){\makebox(0,0)[tc]{$\frac{T}{2}$}}
\put(122,35){\makebox(0,0)[tc]{$T$}}
\put(140,35){\makebox(0,0)[tc]{$t/\mathrm s$}}
\put(16,92){\makebox(0,0)[tr]{$A$}}
}

\put(20,0){\vector(0,1){95}}
\put(15,40){\vector(1,0){130}}
\multiput(19,10)(0,60){2}{\line(1,0){2}}
\put(17,10){\makebox(0,0)[r]{$\scriptstyle -max$}}
\put(17,70){\makebox(0,0)[r]{$\scriptstyle max$}}

\thicklines
\multiput(20,40)(50,0){2}{\line(0,1){30}}
\multiput(70,10)(50,0){2}{\line(0,1){30}}
\multiput(20,70)(50,-60){2}{\line(1,0){50}}
\thinlines

\multiput(70,38)(50,0){2}{\line(0,1){4}}
\put(66,35){\makebox(0,0)[tc]{$\frac{T}{2}$}}
\put(125,35){\makebox(0,0)[tc]{$T$}}
\put(140,35){\makebox(0,0)[tc]{$t/\mathrm s$}}
\put(16,92){\makebox(0,0)[tr]{$B$}}
\end{picture}
\qquad
\begin{picture}(150,210)
\put(0,110){
\put(20,0){\vector(0,1){95}}
\put(15,40){\vector(1,0){130}}
\multiput(19,10)(0,60){2}{\line(1,0){2}}
\put(17,10){\makebox(0,0)[r]{$\scriptstyle -max$}}
\put(17,70){\makebox(0,0)[r]{$\scriptstyle max$}}
\thicklines
\multiput(20,70)(100,0){2}{\line(0,-1){60}}
\put(20,10){\line(5,3){100}}
\put(120,10){\line(5,3){10}}
\thinlines
\multiput(70,38)(50,0){2}{\line(0,1){4}}
\put(70,35){\makebox(0,0)[tc]{$\frac{T}{2}$}}
\put(125,35){\makebox(0,0)[tc]{$T$}}
\put(140,35){\makebox(0,0)[tc]{$t/\mathrm s$}}
\put(16,92){\makebox(0,0)[tr]{$A$}}
}

\put(20,0){\vector(0,1){95}}
\put(15,40){\vector(1,0){130}}
\multiput(19,10)(0,60){2}{\line(1,0){2}}
\put(17,10){\makebox(0,0)[r]{$\scriptstyle -max$}}
\put(17,70){\makebox(0,0)[r]{$\scriptstyle max$}}

\thicklines
\put(20,10){\line(5,6){50}}
\put(70,70){\line(5,-6){50}}
\put(120,10){\line(5,6){10}}
\thinlines

\multiput(70,38)(50,0){2}{\line(0,1){4}}
\put(66,35){\makebox(0,0)[tc]{$\frac{T}{2}$}}
\put(125,35){\makebox(0,0)[tc]{$T$}}
\put(140,35){\makebox(0,0)[tc]{$t/\mathrm s$}}
\put(16,92){\makebox(0,0)[tr]{$B$}}
\end{picture}
\label{funktionen}
\parbox{0.7\textwidth}{\caption{Ausgabefunktionen des Funktionsgenerators.
Links Sinus/Rechteck, rechts Rampe/Dreieck}}
\end{figure}


\subsection{Aufgabe 2 - Nichtrekursives Digitalfilter}

Ein nichtrekursives Digitalfilter (FIR) mit folgenden Differenzengleichungen ist zu
realisieren:
\begin{align}
y_1(n) &= x(n)\\
y_2(n) &= 0{,}5 \left[x(n) + x(n-i)\right]
\end{align}
$y_1$ und $y_2$ sind die Ausgangssignale des Systems, wobei $y_1$ lediglich der
Kontrolle dient. $y_2(n)$ ist das Ausgangssignal des nichtrekursiven Filters,
dessen Aufbau in Abbildung \ref{filter} verdeutlicht wird.
\begin{figure}[h]
\center
\begin{picture}(420,90)
\put(0,50){\makebox(0,0)[l]{$x(t)$}}
\put(25,50){\circle{2}}
\put(26,50){\line(1,0){19}}
\multiput(45,35)(0,30){2}{\line(1,0){30}}
\multiput(45,35)(30,0){2}{\line(0,1){30}}
\put(45,65){\line(1,-1){30}}
\put(54,43){\makebox(0,0)[c]{$A$}}
\put(66,57){\makebox(0,0)[c]{$D$}}
\put(82,55){$x(n)$}
\put(75,50){\vector(1,0){187.5}}
\put(110,50){\circle*{2}}
\put(110,15){\line(0,1){65}}
\multiput(110,80)(35,0){3}{\line(1,0){15}}
\multiput(0,0)(35,0){2}{
\put(135,80){\makebox(0,0){$T$}}
\multiput(125,70)(20,0){2}{\line(0,1){20}}
\multiput(125,70)(0,20){2}{\line(1,0){20}}}
\put(201,80){$\ldots$}
\multiput(220,80)(35,0){2}{\line(1,0){15}}
\multiput(235,70)(20,0){2}{\line(0,1){20}}
\multiput(235,70)(0,20){2}{\line(1,0){20}}
\put(245,80){\makebox(0,0){$T$}}
\put(135,62){\makebox(0,0){$1$}}
\put(170,62){\makebox(0,0){$2$}}
\put(245,62){\makebox(0,0){$i$}}
\put(270,80){\vector(0,-1){22.5}}
\put(270,50){\circle{15}}
\put(270,50){\makebox(0,0)[c]{$+$}}
\put(277.5,50){\vector(1,0){20}}
\put(297.5,35){\line(0,1){30}}
\put(306,49){\makebox(0,0){$0{,}5$}}
\put(297.5,35){\line(3,2){22.5}}
\put(297.5,65){\line(3,-2){22.5}}
\put(320,50){\vector(1,0){20}}
\multiput(340,35)(0,30){2}{\line(1,0){30}}
\multiput(340,35)(30,0){2}{\line(0,1){30}}
\put(340,35){\line(1,1){30}}
\put(349,57){\makebox(0,0)[c]{$D$}}
\put(361,43){\makebox(0,0)[c]{$A$}}
\put(370,50){\line(1,0){19}}
\put(390,50){\circle{2}}
\put(394,50){\makebox(0,0)[l]{$y_1(t)$}}
\put(110,15){\vector(1,0){230}}
\multiput(340,0)(0,30){2}{\line(1,0){30}}
\multiput(340,0)(30,0){2}{\line(0,1){30}}
\put(340,0){\line(1,1){30}}
\put(349,22){\makebox(0,0)[c]{$D$}}
\put(361,8){\makebox(0,0)[c]{$A$}}
\put(370,15){\line(1,0){19}}
\put(390,15){\circle{2}}
\put(394,15){\makebox(0,0)[l]{$y_2(t)$}}
\end{picture}
\caption{Blockschaltbild des FIR Filters}
\label{filter}
\end{figure}
Es wird der Mittelwert des aktuellen Abtastwertes $x(n)$ und einem 
fr\"uheren Abtastwert $x(n-i)$ gebildet. Bei Sinussignalen ergibt sich eine
frequenzabh\"angige Phasenverschiebung, die zu einer Verst\"arkung oder
Ausl\"oschung des Signales f\"uhren kann.

\section{Entwurf und Implementierung}

\subsection{Grundlagen des verwendenten DSP Systems}

Zum Verst\"andnis der Implementierung wird kurz auf das DSP System eingegangen.

\subsubsection{Ablaufsteuerung}

Die Ablaufsteuerung der zu implementierenden Programme geschieht \"uber den
Interrupt 2, welcher \"uber einen Taster ausgel\"ost werden kann.

Bei der Ausl\"osung wird eine Funktion aufgerufen, in der die globale Variable
\texttt{choice} im Bereich $1 \ldots 3$ umlaufen lassen, was f\"ur die 3
verschiedenen Betriebszust\"ande des Funktionsgenerators bzw. des Filters
steht. Außerdem wird die globale Variable \texttt{Run} auf 0 gesetzt.

Im Hauptprogramm l\"auft nach der Initialisierung eine unendliche Schleife
(\texttt{while (1)}), in der mit einem \texttt{switch}-Konstrukt abh\"angig von
\texttt{choice} eine Funktion aufgerufen wird. Vorher wird
\texttt{Run} jedoch auf 1 gesetzt.

Die aufgerufene Funktion ihrerseits enth\"alt eine \texttt{while}-Schleife 
mit der Bedingung \texttt{Run}, l\"auft also so lange bis \texttt{Run} wieder
durch Ausl\"osen des Interrupt 2 zu Null wird.

\subsubsection{Timing}

Das Timing in den eigentlichen DSP Funktionen wird durch Aufruf von
\texttt{idle()} realisiert. Diese Funktion wartet bis zum Ablauf des aktuellen
\texttt{TimerCycle}, welcher eine L\"ange von
$\frac{f_{\text{Takt}}}{\text{SampleRate}}$ Takten hat. Somit k\"onnen in einer
Schleife erst s\"amtliche Berechnungen ausgef\"uhrt werden, was weniger
Takte als einen \texttt{TimerCycle} dauern muss, und dann durch Aufruf von
\texttt{idle()} sichergestellt werden, daß die n\"achste Berechnung erst in der
n\"achsten Sampleperiode durchgef\"uhrt werden. 

\subsubsection{Datenumwandlung mit FloatToByte}

Die Ausgabe der Daten aus den DSP-Funktionen geschieht durch direktes Schreiben
in den Speicherbereich der D/A-Konverter. Da diese allerdings im konkreten
Falle eine Aufl\"osung von 8 Bit aufweisen, und somit einen Integerwert im
Bereich $0 \ldots 255$ erwarten, muss der in den Funktionen berechnete Wert,
der zwischen $-max \ldots max$ liegt umgewandelt werden. 

Per Definition muss $max \leq 1$ sein, also liegt ein umzuwandelnder Wert
maximal im Bereich von $-1 \ldots 1$. Um diesen Bereich nach $0 \ldots 255$ zu
transformieren, muss um 1 verschoben, also 1 addiert werden um in den Bereich $0
\ldots 2$ zu gelangen, und dieser Wert auf $0\ldots 255$ skaliert werden, also
eine Multiplikation mit $127{,}5$. Der gesuchte Wert muss eine ganzzahl sein,
daher wird das Ergebnis als \texttt{int} gecastet.  Die Funktion \texttt{FloatToByte} sieht also folgendermaßen aus: 

\bigskip

\noindent\textbf{Quelltext}

\bigskip

\hrule
\begin{verbatim}
int FloatToByte(float Fl) { return (int) ((Fl + 1) * 127.5); }
\end{verbatim}
\hrule

\bigskip

\subsubsection{Lesen der Daten vom A/D-Wandler}

Beim Einlesen der Werte vom A/D-Wandler erh\"alt man eine 32 Bit Festkommazahl,
in deren 8 MSB sich der Wandlerwert im Bereich von $0\dots 255$ befindet. Um
diesen in die zum Rechnen gebrauchte Zweierkomplementdarstellung umzuwandeln,
sind folgende Operationen von N\"oten:

\texttt{InPortA = InPortA >> 24;}

\noindent Der Inhalt von \texttt{InPortA} wird um 24 Bits nach rechts verschoben. 

\texttt{InportA = ((\~{}InPortA) \& 0xFFFFFF80) | (InPortA \& 0x7F);}

\noindent Umwandlung in Zweierkomplementdarstellung.

\newpage %  tmp

\subsection{Funktionsgenerator}

Die Implementierung des Funktionsgenerators erfolgt wie im vorgegenenen
Quelltext in zwei Funktionen \texttt{sine} und \texttt{slopetriangle}. Diese
wurden jeweils in externen Dateien gespeichert, so daß diese
implementierungsunabh\"angig mit einer festgelegten Schnittstelle aus dem
Hauptprogramm aufgerufen werden k\"onnen.

\subsubsection{Sinus- und Rechteckgenerator}

Die Berechnung des Sinus- und Rechtecksignals geschieht in der Funktion
\texttt{void sine()}. Es gibt eine Variable \texttt{SampleNr}, die in einer
Sekunde von $1$ bis \texttt{SampleRate}, im konkreten Fall $25\,000$ z\"ahlt.
Dies geschieht dadurch, daß nach der Ausgabe jedes Wertes die Funktion
\texttt{idle()} aufgerufen wird, die auf den n\"achsten Timer-Interrupt wartet,
der die Sample-Rate bestimmt. Erreicht der Wert der Wert von \texttt{SampleNr}
den Wert \texttt{SampleRate}, wird ersterer wieder auf 1 zur\"uckgesetzt.

F\"ur jeden dieser Werte von \texttt{SampleNr} muß nun der entsprechende Wert
der Sinusfunktion bei der Frequenz $f$ (gegeben durch die Variable \texttt{int
f}) und mit der Amplitude $max$ (\texttt{float max}, $0 \leq max \leq 1$)
errechnet werden. Die Formel l\"asst sich einfach aufstellen: 
\[f(x) = max \cdot \sin\left(2\pi \cdot f \cdot \frac{x}{R}\right)\] 
wobei $x$ \texttt{SampleNr} und $R$ \texttt{SampleRate} entsprechen. 

Die Rechteckfunktion l\"asst sich schliesslich einfach aus der Sinusfunktion
berechnen: Wenn diese einen negativen Wert annimmt, also der mit
\texttt{FloatToByte} umgewandelte Wert unter 127 liegt, ist der Funktionswert
$-max$, ansonsten ist er $max$.

\bigskip

\noindent\textbf{Quelltext} 

\bigskip 

\hrule
\begin{verbatim}
void sine(int f, float max) {
    long int SampleNr = 1;  /* SampleNr, zaehlt in einer Sekunde bis 25k */
    while (Run) {  
        /* Berechnung des Sinus-Signals mit f bei SampleRate */
        OutPortA = FloatToByte (max * sinf(2*pi*f*SampleNr/SampleRate));
        
        /* Wenn Sinuswert negativ, OutPortB = -max, sonst max */
        OutPortB = (OutPortA<127) ? FloatToByte(-1.0*max) : FloatToByte(max);
        
        /* Naechstes Sample */
        SampleNr++;
        
        /* Verhindern, dass es einen Ueberlauf der Sample-Variable gibt */    
        SampleNr %= SampleRate;
            
        /* Der Prozessor hat diese Befehle deutlich eher berechnet als das
         * naechste Sample ausgegeben werden muss. Daher wird gewartet
         * bis der naechste Timer-Interrupt ausgeloest wird. */
        idle();    

    } /* Ende der while(Run)-Schleife */
} /* Ende von sine() */
\end{verbatim}
\hrule
\newpage

Da zur Vorbereitung des Versuches kein DSP-Board zur Verf\"ugung stand, wurde
das Programm leicht abgewandelt und mit dem GNU gcc getestet. Die Ausgabe
wurde mit Hilfe von GNUplot visualisiert und diente zur \"Uberpr\"ufung der
Richtigkeit der gelieferten Werte. Abbildung \ref{plotsin} zeigt die ersten 100
Werte der Ausgabe von \texttt{sine(500,0.8)}, die den Erwartungen entsprechen.


\begin{figure}
\center
\input{plot}
\caption{Die ersten 100 Werte der Ausgabe der Funktion \texttt{sine(500,0.8)}}
\label{plotsin}
\end{figure}

\subsubsection{Rampe- und Dreiecksgenerator}

Die Funktion zur Berechnung des Rampen- und Dreieckssignales ist der Funktion
zur Generierung des Sinus- und Rechtecksignals sehr \"ahnlich: Auch hier gibt
es eine Laufvariable \texttt{SampleNr}, die in einer Periode von $1$ bis
\texttt{SampleRate} l\"auft. F\"ur jeden Wert dieser Variable wird der Wert der
Rampenfunktion nun anhand folgender Formel berechnet:
\[f(x) = -max + \frac{2\cdot max \cdot f \cdot \left(x\ \%\
\frac{R}{f}\right)}{R}\]
Wobei $x$ \texttt{SampleNr}, $R$ \texttt{SampleRate} entspricht.
Diese Funktion liefert eine verschobene Gerade der Steigung 
$\frac{2\cdot max \cdot f}{R}$, die sich periodisch alle $\frac R f$ 
wiederholt. Die Dreiecksfunktion berechnet sich nun aus dieser Funktion durch
\[g(x) = max - \left|2 \cdot f(x)\right|,\]
wie sich schnell durch grafische Konstruktion anhand der Rampenfunktion
ermitteln ließ.

\bigskip

\noindent\textbf{Quelltext}

\bigskip

\hrule
\begin{verbatim}
void slopetriangle(int f, float max) {
    long int SampleNr = 1;    /* Samplenummer, zaehlt in einer s bis 25k */
    float Slope;              /* Hilfsvariable fuer Rampenfunktion       */
    while (Run) {
        /* Berechnung des Rampensignals als Gerade mit Modulo */
        Slope = -1*max +((2*max*f*(SampleNr%(SampleRate/f)))/SampleRate);
                            
        OutPortA = FloatToByte(Slope);
    
        /* Berechnung der Dreiecksfunktion aus der Rampenfunktion       */
        OutPortB = FloatToByte(max - fabsf(2*Slope));
        
        /* Naechstes Sample                                             */
        SampleNr++;
        
        /* Verhindern, dass es einen Ueberlauf der Sample-Variable gibt */    
        SampleNr %= SampleRate;
            
        /* Der Prozessor hat diese Befehle deutlich eher berechnet als das
         * naechste Sample ausgegeben werden muss. Daher wird gewartet
         * bis der naechste Timer-Interrupt ausgeloest wird. */
        idle();    
    } /* Ende der while(Run)-Schleife */
} /* Ende von slopetriangle() */
\end{verbatim}
\hrule

\bigskip

Auch die Funktion von \texttt{slopetriangle()} wurde anhand von GNUplot
\"uberpr\"uft und verifiziert. Im Praktikum funktionierten die Funktionen  
exakt wie erwartet.


\subsection{FIR Filter}

Das zu implementierende FIR-Filter wurde mit Hilfe eines Ringpuffers
realisiert. Dieser besteht aus einem Array \texttt{Queue} aus maximal 20
Integerwerten und zwei "`Zeigern"' auf die Positionen $n$ (\texttt{p}) 
und $n-1$ (\texttt{p2}) des Ringpuffers. Es handelt sich hierbei jedoch 
nicht um echte Zeiger, die auf eine Speicheradresse verweisen sondern um
Integerzahlen, die die Position des betreffenden Elements im Array angeben.

\noindent In der \texttt{while}-Schleife wird nun 
\begin{itemize}
\item ein Wert vom AD-Wandler eingelesen, in 8 Bit Zweierkomplementdarstellung
umgewandelt und an der Position \texttt p im Array gespeichert,
\item der Eingangswert unge\"andert an den Ausgang A ausgegeben,
\item die Position des Zeigers \texttt{p2} berechnet,
\item der Durchschnitt aus dem Eingangssignal und dem \"altesten Wert des 
Ringpuffers (entspricht \texttt{Queue[p2]}) gebildet und am Ausgang B
ausgegeben,
\item die Position von \texttt p  f\"ur den n\"achsten Durchgang berechnet.
\end{itemize}

\bigskip

\noindent\textbf{Quelltext}

\bigskip

\hrule
\begin{verbatim}
void FilterFIR (int Grad) {
    int p=0;            /* Pseudozeiger auf aktives Element                */
    int p2=0;           /* Pseudozeiger auf n-i                            */
    int Queue[20];      /* Array der Laenge Grad zum Speichern alter Werte */
    int i;              

    for (i=0;i<=Grad;i++){             /* Initialisierung der Queue        */
            Queue[i] = 0;              /* mit Nullen                       */
    }

    while (Run) {
        /* Neuen Wert vom Input einlesen und in 
         * Zweierkomplementdarstellung bringen    */ 
        InPortA = InPortA >> 24;
        InPortA = ((~InPortA) & 0xFFFFFF80) | (InPortA & 0x7F);
            
        Queue[p] = InPortA;            /* neuen Wert ins Array schreiben */

        OutPortA = InPortA;            /* Ungeaenderter Wert fuer A      */
        OutPortA = OutPortA + 127;
        OutPortA = OutPortA << 24;

        /* p2 = Position des Elements n-1, also immer das naechste Element,
         * bzw. bei p=9 das 0. Element */
            
        (p == (Grad)) ? (p2 = 0) : (p2 = p+1); 
            
        OutPortB = (InPortA + Queue[p2])/2;    /* Addition */
            
        OutPortB = OutPortB + 127;
        OutPortB = OutPortB << 24;
    
        p++;                        /* 'Pointer' erhoehen */
        p %= (Grad+1);              /* Ueberlauf verhindern */
            
        idle();
    }
}
\end{verbatim}
\hrule

\bigskip

Im Gegensatz zur ersten Teilaufgabe funktionierte das FIR-Filter nicht auf
anhieb, obwohl auch dessen Funktion durch eine kleine Testroutine vor dem
Praktikum auf korrekte Funktion \"uberpr\"uft wurde. Es stellte sich nach
kurzer Suche heraus, daß der Aufruf der \texttt{idle()}-Funktion fehlte, bzw.
auskommentiert war. Nachdem dies behoben war, funktionierte das Filter wie
gefordert.

Am Oszilloskop war die doppelte Amplitude bei einer Phasenverschiebung von $G
\cdot 2\pi$ und die Ausl\"oschung des Signales bei einer Phasenverschiebung
von $\pi + G\cdot 2\pi$ ($G$: Grad) deutlich zu beobachten. Bei der konkreten
Implementierung mit $G = 10$ und $G=20$ ergaben sich f\"ur die Ausl\"oschung
Frequenzen von respektive $500\,\mathrm{Hz}$ und $1000\,\mathrm{Hz}$ und f\"ur
die Addition von $1000\,\mathrm{Hz}$ und $2000\,\mathrm{Hz}$.

\section{Diskussion}
Die L\"osung der Praktikumsaufgaben stellte dank der genauen Aufgabenstellung
und der vorbereiteten Quelltexte keine große Hürde dar. Bis auf eine vergessene
Auskommentierung in der Funktion zur Implementierung des FIR-Filters
funktionierte alles auf Anhieb. 

Bei der Implementierung des Funktionsgenerators h\"atte man die Laufvariable
\texttt{SampleNr} nicht von $1...$\texttt{SampleRate} sondern nur bis
\texttt{SampleRate/f} (entsprechend einer Periode) laufen lassen k\"onnen;
somit erspart man sich beim Rampengenerator die umst\"andliche
Modulo-Berechnung und gewinnt etwas an Geschwindigkeit. 


\end{document}

