Can Kosar

Kategorie: DSP (page 1 of 4)

Software-Struktur eines Effektblocks

Bei der Audioverarbeitung hat objektorientierte Programmierung große Vorteile. Dazu zählen:

  • Mehrmalige Instanzierung der Sub-Blöcke in anderen Blöcken
  • Mehr Speicherverwaltungsmöglichkeiten
  • Übersichtlichere und verständlichere Programmstruktur

Besonders die Möglichkeit der mehrmaligen Instanzierung der Sub-Blöcke in anderen Blöcken ist ein entscheidendes Argument für die Wahl einer objektorientierten Sprache. Aus dem Grund wurde z.B. das Projekt Flex 500 mit C++ programmiert. Dabei ist jeder Effektblock als eine Klasse realisiert (,die ggf. andere Klassen instanzieren kann).

Die Grundelemente einer Klasse sehen folgendermaßen aus:

Standardinhalt einer Klasse beinhaltet folgende Methoden bzw. Attributen:

  • init() : Hier werden der Block, Subblöcke und Initialparameter initialisiert.
  • start(): Diese Methode wird ausgeführt, wenn ein Block gestartet wird.
  • stop(): Diese Methode wird ausgeführt, wenn ein Block gestoppt wird. (z.B. reset() triggern)
  • set_parameter_xyz(): Mit diesen Methoden, werden die Parameter, die vom Controller mitgeteilt werden verarbeitet und die Klassenparameter aktualisiert.
  • status: Das ist der Flag, ob der Block aktiv oder inaktiv ist.

Simulationsumgebung für DSP-FX

Die Effekte und die Algorithmen auf dem Target zu testen, ist aufwändig. Aus dem Grund ist eine Simulationsumgebung entstanden, um die Algorithmen in Windows Umgebung effizient zu testen.

Das Programm kann als ein Wrapper für die Hauptroutine vorgestellt werden. Es wird hauptsächlich eine Audio-Datei eingelesen und serialisiert. Dann wird der Inhalt der Audio-Datei serialisiert und in die DSP-Hauptroutine gespeist. Das Ergebnis wird dann wiederum deserialisiert und als eine Ergebnisdatei abgelegt.

 

Main-Routine

Der Ablauf der Main-Routine ist im oberen Diagramm gezeigt. Die Implementierung ist folgendermaßen:

Der Rest ist gleich wie im Target code.

Konfiguration / Updates

Auf dem Target werden die Benutzerinteraktionen interpretiert, um die DSP-Einstellungen zu aktualisieren. Bei der Simulation gibt es keine Echtzeit Interaktionen. Daher werden die Module mit erwünschten Einstellungen „initialisiert„. Dann laufen die Module mit diesen festen Einstellungen.

Ausnahme hierzu ist die Aktivierung der Module. Auf dem Target wird ein Wort gesendet, wo jedes Bit auf den Status eines Moduls hinweist. Hierbei muss dieser Hash aktuell manuell modifiziert werden, um die Module zu aktivieren:

Ressourcen

Der Code samt Eclipse-Projekt ist auf Github verfügbar:

Die DSP-Simulation Quellcode vom   herunterladen.

 

 

STM32 SAI Konfiguration

STM32 besitzt je nach Chipvariante eine serielle Audio-Schnittstelle SAI. Durch diese Schnittstelle kann über übliche Protokolle mit Audio-Codecs kommuniziert werden.

Audio-Clocks

Das Codec und die SAI-Schnittstelle müssen synchronisiert werden. Dabei gibt es konkrete Vorgaben bzw. Randbedingungen seitens Codecs.

  • Audio-Abtastfrequenz F_s wählen.
  • Den Multiplikator k_{MCLK} (Codecs haben oft Multiplikatortabellen z.B. 256 oder 512 bei 48kHz) wählen.
  • Daraus die erforderliche Master-Clock-Frequenz berechnen. (Oft F_{MCLK}F_s \cdot k_{MCLK})
  • Master-Clock-Quelle konfigurieren (Externer Quarz bzw. interne Clockquellen)

Übertragung

Unter dem Aspekt gibt es (wie für viele andere Hardwarekomponenten) hauptsächlich drei Möglichkeiten eine Codec-Schnittstelle zu steuern bzw. auszulesen:

  1. Normaler Modus (Blockierend)
  2. Interrupt-Modus (Nicht-blockierend)
  3. Per DMA auslesen (Nicht-blockierend)

Bei einer echtzeitkritischen Audio-DSP-Anwendung kommt nur DMA-Schnittstelle in Frage. Die Konfiguration der Software und Hardware ist hier beschrieben.

SAI und DMA Konfiguration

Beim Flex 500 ist feste 48kHz Abtastrate gewählt. Bei CS4272 kann der Multiplikator 256 oder 512 gewählt werden. Um auch zukünftig 96kHz zu unterstützen wurde hierbei 512 gewählt. Zu Stabilitätszwecken wurde für einen externen Quarz entschieden. Die Frequenz des Quarzes berechnet sich also als

(1)   \begin{equation*} F_{MCLK}=F_S \cdot k_{MCLK} = 48000 \cdot 512 = 24,576 MHz \end{equation*}

In dem Fall ist der Codec der Master und generiert den Bitclock. Der DSP ist Slave und erhält den Bitclock und dazugehörige Streams.

Die Konfiguration sieht folgendermaßen aus:

Low level Treiber: (*_hal_msp.c)

SAI Konfiguration

Starten vom Treiber

DMA Interrupts setzen die Flags. Die Software Architektur ist hier beschrieben.

Die CODEC-Treiber sind hier beschrieben:

CS4272 CODEC-Schnittstelle für Nucleo H743

WM8731 CODEC-Schnittstelle für Nucleo H743

Ressourcen

Der vollständige Code vom Communication Stack befindet sich auf den Repositories vom Controller und DSP unter den Ordnern „hw„.

DSP – Targetcode vom herunterladen

Controller- Targetcode vom   herunterladen

DSP<->Controller Kommunikation

Die Hardwarearchitektur vom Flex 500 ist hier beschrieben. Die Kommunikation zwischen DSP und Controller läuft über eine SPI-Schnittstelle. Auf der Controller-Seite werden Mitteilungen interrupt-gesteuert gesendet, da die Mitteilungen von mehreren Instanzen aus geschickt werden können und DMA zu ständigen Unterbrechungen auf der DSP Seite führen würde.

Auf der DSP-Seite wird die Mitteilung, die über SPI-Schnittstelle erhalten werden, mit DMA an den Programmspeicher kopiert. Danach wird ein Interrupt ausgelöst, wonach der DSP den Befehl verarbeiten kann.

Die Struktur der Mitteilung

Die Struktur der Mitteilung ist im folgenden dargestellt.

  • Bank Id: Die ID der Effekt-Bank.
  • Type: Der UI-Controller type, der sich geändert hat: Encoder oder Button
  • Id: Der ID des UI-Controllers (Button number oder Encoder number)
  • Data: Data in (Float, 32bit, 16bit, 8bit mit oder ohne Vorzeichen)

Die Übertragung der Mitteilung

Die Mitteilung liegt im Programm im Typ „union“, der die Typen

  • Float
  • 32bit unsigned
  • 16bit unsigned
  • 8bit unsigned

beinhaltet.

Daten dieses Typs müssen über SPI übertragen werden. Dabei wird der Vorteil genutzt, dass sowohl Sender als auch der Empfänger gleiche Endianness benutzt. (Beide Cortex-M7) Das heißt, wir können einfach den Speicherbereich, wo die Mitteilung liegt, schicken. Dann castet der Empfänger auf dieselbe Union zurück und die Daten liegen in der erwünschten Struktur beim Empfänger an.

Code vom Sender

wobei ctrl_tx des Typs „Unions“ ist.

Code vom Empfänger

Ressourcen

Der vollständige Code vom Communication Stack befindet sich auf den Repositories vom Controller und DSP unter den Ordnern „com„.

DSP – Targetcode vom herunterladen

Controller- Targetcode vom   herunterladen

Hardware Architektur – Verteiltes System

Übliche Systemarchitektur eines Audioprozessorsystems wie das vom Flex 500 ist ein verteiltes System. Die Aufgaben werden dabei auf mehrere Prozessoren verteilt. Bei einer leistungs- und echtzeitkritischen Anwendung wie ein Audio-Prozessor ist das oft unverzichtbar.

Hardware-Architektur

Die Hardware-Architektur vom Flex 500 ist im folgenden Bild gezeigt:

Audio-DSP

Spezialisierte Audio-DSP-Chips

Der Kern eines typischen Audio-Prozessors ist ein DSP-Chip. Lange Zeit wurden dafür ausschließlich dafür konzipierte Audio-DSPs, wie z.B. die von Texas Instruments und Analog devices eingesetzt. Die Audio-DSPs haben Befehlsätze, die für Audiodatenverarbeitung typisch sind und eine effiziente hardwaregestütze Verarbeitung der Daten ermöglichen. Dazu gehören viele spezialisierte SIMD-Befehle. (Single Instruction Multiple data) wie MACs (Multiplier+Accumulator). Diese ermöglichen schnelle Verarbeitung von z.B. Biquad-Filtern, ein sehr verbreitetes digitales Filter oder aber auch viele andere Algorithmen, wo sequentielle Multiplikation und Addition-Folgen größerer Daten nötig ist.

MehrZweck-Mikroprozessoren (General purpose Microprocessors)

Mittlerweile sind die Leistung und die Befehlssätze der Mikroprozessoren rasant gestiegen. Heutzutage sind viele Prozessoren mehrere Hundert Megahertz schnell getaktet und bieten u. a. DSP-Einheiten für SIMDs und Gleitkommazahl-Einheiten (FPUs). Aufgrund ihrer breiten Verfügbarkeit und vielseitiger Einsatzmöglichkeiten jenseits der Audio-Verarbeitung, sind die Mehrzweck-Mikroprozessoren zu einer echten Alternative gegenüber der herkömmlichen Audio-DSPs geworden.

Ein Spitzenreiter unter denen ist die Prozessoren auf Basis von ARM Cortex-M7. Diese Prozessoren sind bis zu 600MHz getaktet, besitzen DSP und FPU Einheiten. Ein Vergleich von Cortex-basierten Prozessoren gegenüber der herkömmlichen, verbreiteten Produkte von Texas Instruments und Analog devices ist im folgenden Artikel detailliert aufgeführt:

Choosing the best processor for your DSP application

DSP capabilities of Cortex Processors

In diesen Studien ist sichtbar, dass die High-End Spezialprozessoren für manche spezialisierte Tasks wie MAC-Leistung immer noch die Nase vorne haben. Allerdings sind die Cortex Prozessoren auch sehr leistungsfähig und können ihre Stärken bei allgemeineren Tasks spielen, wofür die Spezialprozessoren keine HW-Unterstützung anbieten.

Aus den genannten Aspekten wurde für den Flex 500 ein STM32H743 mit 400MHz Taktrate, DSP und FPU-Einheiten gewählt.

Controller-Chip

Ein Controller-Chip übernimmt oft alle sonstigen Aufgaben wie die allgemeinen Verwaltungsaufgaben, GUI-Steuerung, Anzeige etc. Die Echtzeitansprüche an den Controller-Chip ist niedrig, dafür muss er viele Tasks abarbeiten. DSP und Controller-Chips unterscheiden sich voneinander vor allem in deren Softwarearchitektur.

Der DSP-Chip muss mehrere Tasks schedulen und abarbeiten. Eine Middleware wie FreeRTOS ist dafür sehr gut geeignet, wenn die Komplexität und die Tasks steigt. Man kann auch „Bare-metal“ programmieren und eigenen Scheduler schreiben.

Je nach benötigter Leistung und Peripherien kann man einen Mikroprozessor wählen, der diese Aufgaben erledigt. Auch hierbei  ist ARM Cortex-M sehr gut geeignet und verbreitet.

Beim Flex 500 muss der Mikrocontroller

  • GUI Inputs und Outpus managen
  • Eine kleine Grafikbibliothek treiben
  • Kommunikation zum DSP aufbauen.
  • Expression-Pedal und Fußschalter treiben
  • Sonstige HW steuern (Leistungsstufe etc.)

Als Controller von Flex 500 ist der STM32F767 von ST gewählt, der mit 216MHz Taktfrequenz und zahlreiche Schnittstellen all diese Aufgaben erledigen kann. Für diesen Zweck ist vermutlich auch ein kleinerer Cortex-M4 vollständig ausreichend.

Display-Treiber

Der Controller-Chip ist von der Prozessorleistung her sehr stark und besitzt  auch ein Display-Treiber. Allerdings ist die verfügbare interne RAM mit 512 sehr knapp für Grafikanwendungen. Um die interne RAM für sonstige Aufgaben freizuhalten, ist ein externer Display-Treiber gewählt.

Die Lösung für Display hängt stark von Anforderungen an. Eine sehr gute Übersicht ist im folgenden Paper von ST verfügbar:

LCD-TFT display controller (LTDC) on STM32 MCUs

Beim Flex 500 ist ein Display mit integriertem Chip ILI9341 eingesetzt.

 

Tuner: Darstellung der Noten und Abweichungen

Nachdem die Frequenz ermittelt wurde, muss nun dem Anwender dargestellt werden, welche Soll-Note erkannt wurde und wieviel die berechnete Ist-Note von der Soll-Note abweicht, damit dieser sein Instrument stimmen kann.

Abschätzung der Soll-Note

Dabei wird gesucht, welcher Notenfrequenz, die gemessene Frequenz am nächsten ist. Da die Frequenzskala logarithmisch ist, müssen die Schranken von einzelnen Noten bestimmt werden, die von der Hauptfrequenz +-50 cent entfernt sind.

Berechnung der Schranken

Die Schranken, die 50 cent von der Hauptfrequenz entfernt sind, werden zunächst einmalig berechnet. Wenn sich die Kammertonfrequenz f_{KT} (z.B. A4=440Hz) ändert, müssen die Schranken wieder berechnet werden, aber nicht in jedem Messzyklus.

Die Frequenzen der Hauptnoten berechnen sich als

(1)   \begin{equation*} \begin{aligned} f_{i,50c}&=\frac{f_{KT}}{ 2^{\frac{i}{12}}}\\ i&\in \mathbb{N} \end{aligned} \end{equation*}

Dann können wir die 50 cent verschobene Frequenzen als

(2)   \begin{equation*} \begin{aligned} f_{i,50c}&=\frac{f_{KT}}{ 2^{\frac{i-0.5}{12}}} \\ i\in \mathbb{N} \end{aligned} \end{equation*}

berechnen.

Die Implementierung kann folgendermaßen realisiert werden:

Suche nach den Schranken

Dafür eignet sich der binäre Suchalgorithmus, wenn wir keine Vorschätzung haben. Falls wir eine Vorschätzung haben (ein guter Ansatz ist die letzte gemessene Schranke zu nehmen) eignet sich auch die lineare Suche.

Im folgenden ist die Implementierung mit einem modifizierten binären Suchalgorithmus gezeigt.

Berechnung der Abweichung

Die Abweichung von der Soll-Note kann folgendermaßen berechnet werden.

Für die Zuordnung des Notenindex zur Notenbezeichnung ist ein String-Array hinterlegt.

Der gesamte Ablauf der Notenfindung sieht also folgendermaßen aus.

Ressourcen

Vollständigen Target-Code der Tuner-Anzeige vom Github herunterladen.

Eine effiziente Methode zur Frequenzerkennung

Die Frequenzerkennung (engl. Pitch detection, frequency detection) ist ein Prozess, mit dem man die dominantesten Frequenzen eines Audio-Abschnitts berechnet. Hierbei muss betont werden, dass ein Audio-Abschnitt nicht eine einzige Schwingung mit einer Frequenz besitzt. Er ist vielmehr eine Zusammensetzung von vielen Grundschwingungen, die sich zu jedem Zeitpunkt ändert. Wir nehmen z.B. die Gitarre: Wenn wir eine Saite zupfen, entstehen mehrere harmonische Grundschwingungen und ein paar nicht-harmonische Störschwingungen, die der Natur des Instruments geschuldet sind. Wir hören quasi ein „Bouqet“ aus Grundschwingungen. Wir als Menschen, nehmen deuten (zumindest oft) die dominanteste Schwingung als Grundfrequenz wahr. Die Aufgabe ist nun also diese Grundfrequenzen herauszufinden. Das ist eine sehr heikle Aufgabe, weil

  • die Amplituden der dominantesten Schwingungen sich kontinuierlich ändern
  • die dominanteste Schwingung sich im Laufe des Sample-Abschnitts ändern kann, (obwohl der ganze Sample auf eine Note deuten kann)

Die Anforderungen an einen Tuner können folgendermaßen zusammengefasst werden:

  • Aktualisierungsrate > f_s=10Hz
  • Minimale Frequenz: f_{min}=27,5Hz (A0)
  • Maximale Frequenz= f_{max}=440Hz (A4)
  • Messgenauigkeit:= \Delta_{max}=1cent
  • Effiziente Berechnung (abhängig vom eingesetzten DSP)

Mathematische Ansätze

Es gibt verschiedene mathematische Ansätze für die Umsetzung der Frequenzbestimmung:

  • Fourier-Transformation
  • Hartley-Transformation
  • Auto-Correlation

Die Fourier und Hartley-Transformationen basieren auf den Ansatz, dass man das Signal erst in Frequenzbereich transformiert und das Frequenzspektrum ermittelt. Für niedrige Frequenzen kann das eine geeignete Methode sein, um 1 Cent Genauigkeit zu erreichen. Allerdings wird die Transformation sehr langsam, wenn wir das Spektrum über mehrere 100Hz mit Hundertstel-Hertz-Genauigkeit ermitteln wollen. Denn die Genauigkeit der Fourier-Transformation müssen wir über das ganze Spektrum festlegen. Anders bei der Auto-Korrelation:

Auto Korrelation

Das Prinzip der Auto-Korrelation (Selbst-Übereinstimmung) ist relativ einfach. Wir multiplizieren unser Signal mit sich selbst mit einem zeitlichen Offset. Danach erhalten wir einen skalaren Wert. Dieser Wert gibt uns eine quantitative Aussage darüber, wie das Signal bei diesem Offset mit sich selbst übereinstimmt. Wenn wir dann die Offsets finden, bei denen die Korrelation am Höchsten ist, können wir daraus die Frequenzen ermitteln. Die Frequenz wäre dann einfach der Kehrwert vom Offset.

Der Algorithmus sieht folgendermaßen aus:

(1)   \begin{equation*} AC(\delta)=\sum_{i=1}^{n} y(i) \cdot y(i-\delta) \end{equation*}

oder wenn das Signal im Puffer vorliegt, auch vorwärts berechnend:

(2)   \begin{equation*} AC(\delta)=\sum_{i=1}^{n} y(i) \cdot y(i+\delta) \end{equation*}

Dann erhalten wir einen Auto-Korrelationswert AC für das Offset \delta. Die Anzahl der Samples n, über die wir diesen Wert berechnen muss

  • so groß wie möglich sein, um niedrige Frequenzen abbilden zu können und
  • so klein wie möglich sein, um die Anzahl der Multiplikationen zu reduzieren.

Wenn wir bei der Messung von f_{max}=440Hz eine Genauigkeit von 1 cent haben wollen, bedeutet das eine Frequenzgenauigkeit F_{\Delta}

(3)   \begin{equation*} F_{\Delta}=440 \cdot 2^{\frac{1}{1200}}-440=0.254Hz \end{equation*}

Auf der anderen Seite, wenn wir tiefe Frequenzen gut auflösen wollen, brauchen wir einen Sampleabschnitt, der ein paar volle Schwingungen beinhaltet. Bei 4 mal 20Hz Schwingungen ergibt sich eine Sample-Anzahl von

(4)   \begin{equation*} n_{27,5Hz}=48000/27,5 \cdot 4= 6981 \end{equation*}

Also wenn wir den ganzen Frequenzbereich mit 0,3Hz Genauigkeit scannen und daraus die Autokorrelation von allen Frequenzen mit 9600 Samples berechnen, beträgt der Rechenaufwand

(5)   \begin{equation*} O_{MAC}=[(440-27,5)/0.28] 6981=10284508 \end{equation*}

Multiplikation und Additionen. Das bedeutet rund 10,2Mio. Operationen pro Scan. Bei einer Aktualisierungsrate von f_S=10Hz brauchen wir 200Mio. Rechenoperationen pro Scan – nur um Autokorrelationswerte zu berechnen. Also dieser primitiver Ansatz ist nicht geeignet, um die Frequenz auf eine effiziente Weise zu berechnen.

Ein effizienter Ansatz zur Frequenzbestimmung

Wenn wir nur an ein paar dominanten Frequenzen interessiert sind, müssen wir nicht die Auto-Korrelationswerte für das ganze Spektrum mit feiner Auflösung berechnen. Daraus entstehen einige Optimierungsansätze: Man kann erst den groben Bereich der dominanten Frequenzen berechnen und dann diese in dem Bereich suchen. Dieser Ansatz ist im unteren Diagramm dargestellt.

Die einzelnen Schritte dieses Ansatzes ist im Folgenden erklärt.

Das Spektrum RASTERN

Zuerst wollen wir das Spektrum zwischen A0 und A4, also 27,5Hz und 440Hz in so wenig wie möglich Raster teilen, bei denen wir uns sicher sind, dass nicht mehrere dominante Frequenzen drin vorkommen können. Im vorliegenden Fall möchten wir die dominante Frequenz eines Musikinstruments, konkreter Gitarre und Bassgitarre ermitteln. Für den Zweck bietet es sich an, die chromatischen Notenfrequenzen als Raster zu nehmen. Unten sind die Noten von 27,5Hz bis 440 Hz dargestellt.

Notenfrequenzen

Notenfrequenzen

Das heißt, wir können das Spektrum, dass uns interessiert, erstmal in 49 Blöcke teilen. Dann berechnen wir nur den AC-Wert, der uns interessiert. Wir ermitteln hier die Information: „Um welche Note geht’s denn überhaupt?

Zudem: Die Wahl der Noten nach chromatischen Frequenzen bei 440Hz-Stimmung, ist auch eine strategische Wahl. Denn: Die gefundenen Noten aus diesem Array werden genommen, um in ihrer Nähe nach der exakten Frequenz zu suchen. Diese Noten sind auch die erste Schätzung für die Suche. Mit der Annahme, dass dieser Algorithmus als chromatischer Tuner bei einem 440Hz-gestimmten Instrument benutzt werden soll, erwarten wir oft eine Übereinstimmung in unmittelbarer Nähe.

Zunächst werden diese Frequenzen als Offsets \delta in einem Array gespeichert:

(6)   \begin{equation*} \delta(f)=f_s/f \end{equation*}

Wir denken ab nun nicht mehr in Frequenzen f sondern in Offsets \delta.

Erforderliche Sample-Anzahl n für die Raster berechnen

Wie bereits erwähnt, brauchen wir bei tieferen Frequenzen mehr Samples für die Auto-Korrelation-Berechnung als bei höheren Frequenzen. Dafür muss man sich eine Art Güte ausdenken. Wenn man sich z.B. für eine Güte von 4 vornimmt, bedeutet das, dass man 4 volle Schwingungen einer Frequenz für die Auto-Korrelationsberechnung braucht. Das ergibt

(7)   \begin{equation*} n(\delta)=[f_s/f] \psi \end{equation*}

bei der Güte \psi.

Die Implementierung würde folgendermaßen aussehen:

Auto-Korrelationswert berechnen

Die Berechnung vom Auto-Korrelationswert ist oben beschrieben. Je nach Hardwareressourcen kann dies z.B. folgendermaßen implementiert werden.

Auto-Korrelationswert normieren

Wir haben variable Anzahl an Samples für jede Frequenz, da wir erstens tiefe Frequenzen erfassen können, andererseits bei höheren Frequenzen Ressourcen sparen wollen. Daraus ergeben sich Auto-Korrelationswerte, die nicht untereinander vergleichbar sind. Wir normieren diese deshalb nach der Länge der Samples, mit denen sie berechnet wurden. Damit kann man die Amplituden einzelner Raster miteinander vergleichen.

Extrema des Spektrums (PEAKS) ermitteln

Jetzt haben wir das Spektrum, d.h. die AC-Werte für jeden Rasterpunkt, ermittelt. Wir müssen nun dort herausfiltern, welche Offsets wir brauchen. Dieser Teil ist etwas „tricky“ und beruht auf folgenden Erfahrungswerten für Gitarren und Bass-Stimmung:

  1. Bei einer Einzelsaite entstehen u. a. zwei deutlich dominante Peaks.
  2. Die Peaks sind die ersten Harmonischen voneinander (f_1/f_2=2)
  3. Die Amplituden der beiden Peaks sind sehr ähnlich und wechseln sich oft beim Ausklingen.
  4. Der tiefere Peak ist aussagekräftiger, da man dadurch die Frequenz (ohne Oversampling) präziser messen kann (n_2>n_1)

Deshalb versuchen wir im folgenden Schritt

  1. Zwei dominanteste Offsets (Peaks) aussuchen
  2. Diese sortieren

Das wird folgendermaßen implementiert.

Nach den exakten Frequenzen (Offsets) suchen

Wir haben nun zwei Frequenzen ermittelt, bei denen wir wissen, unsere gesuchte Frequenz liegt in deren unmittelbare Nähe. Man kann sich das ungefähr folgendermaßen vorstellen:

Rendered by QuickLaTeX.com

Es existieren zahlreiche Suchalgorithmen. Je nach Anwendung können diese Vor- und Nachteile haben. Eine Übersicht ist hier verfügbar. In dem Fall bieten sich hauptsächlich zwei Suchalgorithmen

Die Wahl zwischen diesen Algorithmen sind im vorliegenden Fall schwierig. Wenn die gesuchte Frequenz in der Ferne liegt, ist binäre Suche vorteilhafter. Wenn diese aber in der Nähe liegt, ist lineare Suche vorteilhafter.

Die Implementierung dieser Suche ist folgendermaßen zu realisieren:

Stochastische Abschätzung der GESUCHTEN Frequenz

Nachdem die exakten zwei Frequenzen ermittelt wurden, merkt man, dass dieser Wert in einem kleinen Intervall schwankt, was der Natur der Instrumente geschuldet ist. Nun muss

  • die Plausibilität der berechneten Frequenzen bewertet und
  • der Frequenzverlauf gemittelt werden,

damit der Benutzer eine vernünftige Frequenz- bzw. Notenanzeige hat. Dafür wird eine Toleranz \sigma eingeführt, innerhalb dessen die Frequenz schwanken darf. Wenn eine Messung diese Toleranz überschreitet, vermutet man ein Notenwechsel. Also entweder wird eine andere Saite gezupft oder so stark korrigiert, dass man diese Messung nicht mehr in die Mittelung einfließen darf. Danach wird die Mittelung zurückgesetzt und weiterhin auf eine Messungfolge gewartet, wo alle Messungswerte innerhalb der Toleranz liegen. Danach wird die Note wieder gezeigt.

Die Implementierung kann folgendermaßen aussehen:

Ressourcen

Hiermit wurde der Ablauf einer Frequenzermittlung für eine Tuner-Applikation gezeigt. Der vollständige Target-Code für DSP und der Simulationscode für Windows kann vom GitHub heruntergeladen werden.

Digitaler Tuner (Stimmgerät)

Ein Effektgerät besteht hauptsächlich aus den Komponenten

Im Falle von einem verteilten System wird die Frequenzerkennung auf dem DSP durchgeführt. Die erkannte Frequenz wird dem Controller mitgeteilt, der dann Korrektur-/Stimmhilfe gibt. Dazu gehört

  • die Erkennung/Vermutung der Soll-Note
  • Die Abweichung von der vermuteten Soll-Note.

Die Erkennung der Frequenz bzw. die Darstellung der Abweichung ist in obigen Kapiteln ausführlich beschrieben.

DSP Architektur

Ein DSP-FX benötigt jitter-freie Verarbeitung der Audiodaten in Echtzeit.  Echtzeit ist ein theoretischer und dehnbarer Begriff, der die erforderliche Systemlatenz beschreibt. Beim Audio beruht diese Feststellung auf die Latenzwahrnehmung des Menschen. Wir nehmen die Latenzen unter 10ms als Echtzeit wahr. Die Latenz der gesamten Kette (Roundtriplatenz) ist die Zeit zwischen dem Eintreten des Signals in die Codec-Eingänge bis zur Bereitstehung des verarbeiteten Signals an Codec-Ausgängen. Diese Zeit darf 10ms nicht überschreiten. Am besten soll diese Zeit sogar noch unter 10ms liegen, da auch der Schall zwischen den Lautsprechern und dem Ohr zusätzlich große Latenz erzeugt und Echtzeiterlebnis schnell beeinflusst werden kann.

Systemarchitektur

Die Übersicht der Systemarchitektur des DSPs vom Flex 500 ist im folgenden Diagramm gezeigt:

Codec

Ein Audio- Codec (Coder, decoder) ist die Komponente, die die analogen Audio-Signale ins Digitale wandelt und die digitalen Audio-Signale ins Analoge wandelt. (Sampling) Er besteht aus einem oder mehreren Analog-Digital-Wandlern (ADC) und ein Digital-Analog-Wandlern (DAC). Nach diesem Schritt liegen die Audio-Signale in einem digitalen Audio-Format vor, im vorliegenden Fall als I2S-Format (Intersound).

Die Codecs müssen konfiguriert und initialisiert werden. Das erfolgt über eine andere serielle Schnittstelle, üblicherweise SPI oder I2C. Das heißt, der Codec hat auch eine Steuerschnittstelle zum DSP. Beim Flex 500 stehen beide Schnittstellen zur Verfügung.

SAI

SAI (Serial Audio Interface) ist eine Schnittstelle, über die digitale Audio-Daten ausgetauscht werden können. Der Codec kommuniziert mit der SAI-Schnittstelle vom DSP-Chip, in dem Fall STM32H743. Diese Schnittstelle serialisiert und deserialisiert die Audiodaten, D.h. er schreibt/liest die in den bzw. von dem Arbeitsspeicher.

 

DMA

Das Schreiben bzw. Lesen muss über eine DMA (DIrect memory access)-Hardware-Komponente erfolgen. DMA ist eine einfache Hardware, die die Aufgabe hat, ein Register in das andere zu kopieren. Die Startaddresse, FIFO, IRQs und die Länge müssen dabei konfiguriert werden. Dadurch dass DMA die Datenübertragungsaufgabe übernimmt, kann sich DSP auf die Datenverarbeitung konzentrieren.

DMA muss so konfiguriert werden, dass er ein Interrupt auslöst, wenn die Puffer

  • halb voll und
  • ganz voll

sind. Dadurch können die Flags der Zustandsmaschine (State machine) gesetzt werden.

Puffer

Das vom Audio-Codec ins digitale I2S-Format gewandelte Audio-Signal muss in einem Eingangspuffer zwischengespeichert werden. Dann wird dieses Puffer vom DSP verarbeitet und das Ergebnis in ein Ausgangspuffer geschrieben. Die Größe der Puffer ergibt sich aus dem Kompromiss aus zwei Anforderungen:

  1. Die Puffer muss so klein wie möglich sein, um eine nicht-wahrnehmbare Latenz zu erreichen.
  2. Die Puffer müssen so groß wie möglich sein, um eine effiziente blockweise Datenverarbeitung zu ermöglichen (Overhead muss reduziert werden)

Bei den Anforderungen

  • Roundtrip-Latenz = 10ms
  • Abtastrate f_S= 48kHz
  • Bittiefe = 32bit

ergibt sich eine Puffergröße von 240 für jeweils Eingangs- und Ausgangspuffer mit 32bit Registern, da Eingangslatenz und Ausgangslatenz 5ms betragen müssen.

Für die Verarbeitung mit DMA-Interrupts wird ein Doppelpuffer der Größe 480 verwendet. Für genaue Erkläreung, siehe unten.

Zustandsmaschine

Die Zustandsmaschine ist die Hauptsteuerungskomponente  in der Software. Durch die Interrupts von DMA wird der Software mitgeteilt, dass das Puffer halb oder ganz voll ist. Nun kann die Zustandsmaschine, die in Endlosschleife läuft, entscheiden, ob der Prozess getriggert werden soll.

 

Die auf STM32 eingesetzter DMA unterstützt Double-buffering. Das heißt, er kann auf der Hälfte und am Ende der Übertragung ein Interrupt auslösen. Deshalb müssen wir das Doppelpuffer nicht selbst managen.

Der Ablauf sieht folgendermaßen aus:

  1. Erste Hälfte vom RX fertig ( Ab nun beschreibt DMA die zweite Hälfte)
  2. Erste Hälfte vom TX fertig ( Ab nun beschreibt DMA die zweite Hälfte)
  3. Zustandsmaschine löst die Verarbeitung der ersten Hälfte aus. Jetzt liest DSP von der ersten Hälfte von RX und beschreibt die erste Hälfte von TX.
  4. Zweite Hälfte vom RX fertig ( Ab nun beschreibt DMA die erste Hälfte)
  5. Zweite Hälfte vom TX fertig ( Ab nun beschreibt DMA die erste Hälfte)
  6. Zustandsmaschine löst die Verarbeitung der zweiten Hälfte aus. Jetzt liest DSP von der zweiten Hälfte von RX und beschreibt die zweite Hälfte von TX.
  7. Zurück zu 1.

Man erkennt, dass zwischen den RX und TX interrupts ein kleiner Versatz ist. Zwar synchronisiert der Codec die ADCs und DACs aber trotzdem entstehen ein kleines Offset von ein paar Samples. Um den Jitter zu verhindern, müssen beide Interrupts ausgewertet werden, um sicherzugehen, dass in der zu verarbeitenden Hälfte wirklich nichts mehr beschrieben bzw. gelesen wird.

Implementierung

Zuerst müssen die Stati initialisiert werden.

 

Die Hauptroutine, die Endlosschleife der Verarbeitung wird folgendermaßen implementiert:

Die Flags tx_status und rx_status wurden in Interrupt Routinen gesetzt und hier (nach der Verarbeitung) wieder geresettet.

Wichtig: Die Interruptroutine muss so schnell wie möglich ablaufen, da diese höchste Priorität hat und alles pausiert. Hier nichts verarbeiten, sondern nur Flags setzen, die dann in der Hauptschleife verarbeitet werden.

 

 

WM8731 CODEC-Schnittstelle für Nucleo H743

Die WM8731 ist ein Budget-Klasse-Stereo Audio-CODEC von Cirrus Logic für mobile Applikationen. Es unterstützt 24bit Bittiefe, bis zu 96kHzAbtastrate und besitzt einen dynamischen Bereich vom 90db für ADC und 100dB für DAC .

Für Flex 500 wurde ein Breakout-Board für WM8731 entwickelt, das man direkt auf ein Nucleo Board stecken kann.

Board-Design

Folgendes auf Referenzdesign basiertes Design wurde für das Breakout board entwickelt.

Analoge Ein- und Ausgänge

Da WM8731 für mobile Anwendungen mit knappem Platz entwickelt ist, kommt es ohne zusätzlichen Eingangs- und Ausgangsstufen klar. Dafür unterstützt es nur unsymmetrische Ein- und Ausgänge.

Dafür hat es einen integrierten Kopfhörerverstärker. Auf dem Board befinden sich Line-In/Out und Kopfhörer-Anschlüsse.

Oszillator

Ein 24,576 MHz Oszillator ist eingebaut und gibt den Takt an den Master-Clock.

Ressourcen

Die KiCad Schaltung- und PCB-Designdaten herunterladen

Kompletter Programmcode für H743-Target

ältestenposts

Rechte © 2024 Can Kosar

Mit Unterstützung von Wordpress, QuickLaTeX und Design von Anders NorenSeitenanfang ↑