Can Kosar

Tag: STM32

STM32 ADC: Expression-Pedal Steuerung

STM32 hat je nach Chipvariante einen oder mehrere Analog-Digital-Wandler (ADC).

Der ADC konvertiert die am analogen Eingang anliegende Spannung in einen binären Wert. Dafür braucht er allerdings ein Paar Zyklen- je nach Einstellung aber mindestens um die 3 Zyklen. Zudem läuft ADC mit niedrigerer Frequenz als der Cortex-Kern. Man muss also wissen, dass eine Konvertierung einige Prozessorzyklen lang dauert.

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

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

In diesem Modus läuft das Sampling im Hauptprogramm. Das heisst, man triggert das Sampling an, wartet(!) darauf, dass das Sampling fertig ist und macht weiter im Hauptprogramm. Das bedeutet viele verlorene Zyklen und darf nur in Ausnahmefällen bzw. in nicht leistungskritischen Applikationen eingesetzt werden.

Interrupt Modus

Im Interrupt-Modus stößt das Hauptprogramm das Sampling an und macht weiter mit seinen Aufgaben. Wenn das Sampling fertig ist, löst die ADC-Hardware einen Interrupt aus. Mit Hilfe dieses Interrupts kann dann dem Hauptprogramm mitgeteilt werden, dass nun das Register den neuen Digitalwert beinhaltet.

DMA MODUS

Im DMA-Modus schaufelt die DMA-Hardware im Hintergrund die ADC-Werte. Dabei wird die ADC-Hardware in den kontinuierlichen Modus gesetzt. Das heißt, sie fängt gleich mit dem nächsten Sampling an, wenn sie mit einem fertig ist. Das ist oft der effizienteste Modus.

 

Beim Projekt Flex 500 wird z. B. das Expression-Pedal mit einem ADC interrupt-gesteuert gelesen. Der Grund dafür ist, dass man die Frequenz mit einem Timer einstellen möchte.  Die ADC-Hardware ist folgendermaßen konfiguriert. Das

Nach jedem Lesezyklus triggert man dabei das nächste Sampling. Das Interrupt wird dabei allerdings nicht benutzt, da der Zyklus kurz ist und das Interrupt sonst das Hauptprogramm unnötig oft unterbrechen würde.

Andere Möglichkeit wäre eben per DMA, dass man dabei die Hardware nur starten und stoppen muss:

Die Konfiguration des Timers ist folgendermaßen. Bei einer CPU-Frequenz von 216MHz ergibt sich eine Timerfrequenz von:

(1)   \begin{equation*} f_{TIM3}= \frac{216\cdot 10^6}{(1600+1)*(10000+1)} \approx 13.5Hz \end{equation*}

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

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.

 

 

Rechte © 2024 Can Kosar

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