Hallo Freunde!In früheren Artikeln habe ich
über mein Projekt und
seinen Softwareteil gesprochen . In diesem Artikel werde ich Ihnen erklären, wie Sie einen einfachen Signalgenerator für 4 Kanäle erstellen - zwei analoge Kanäle und zwei PWM-Kanäle.

Analoge Kanäle
Der Mikrocontroller
STM32F415RG enthält einen 12-Bit-
DAC -Wandler (Digital-Analog) in zwei unabhängige Kanäle, mit dem unterschiedliche Signale erzeugt werden können. Sie können Daten direkt in die Register des Konverters laden, dies ist jedoch nicht sehr geeignet, um Signale zu erzeugen. Die beste Lösung besteht darin, ein Array zu verwenden, in das eine Signalwelle erzeugt wird, und dann den DAC mit einem Trigger vom Timer und DMA auszuführen. Durch Ändern der Frequenz des Timers können Sie die Frequenz des erzeugten Signals ändern.
Zu den „
klassischen “ Wellenformen gehören: Sinus, Mäander, Dreieck und Sägezahn.

Die Funktion zum Erzeugen dieser Wellen im Puffer ist wie folgt In der Funktion müssen Sie einen Zeiger auf den Anfang des Arrays, die Größe des Arrays, den Maximalwert und die gewünschte Wellenform übergeben. Nach dem Aufruf wird das Array mit Samples für eine Welle der gewünschten Form gefüllt, und Sie können den Timer starten, um den neuen Wert regelmäßig in den DAC zu laden.
Der DAC in diesem Mikrocontroller hat eine Einschränkung: Die typische Einschwingzeit (die
Zeit vom Laden eines neuen Werts in den DAC bis zum Erscheinen am Ausgang ) beträgt 3 ms. Aber nicht alles ist so einfach - diese Zeit ist maximal, d.h. Wechsel von Minimum zu Maximum und umgekehrt. Beim Versuch, den Mäander zurückzuziehen, sind diese verunreinigten Fronten sehr deutlich sichtbar:

Wenn eine Sinuswelle ausgegeben wird, ist die Verstopfung der Fronten aufgrund der Wellenform nicht mehr so auffällig. Wenn jedoch die Frequenz erhöht wird, verwandelt sich das sinusförmige Signal in ein dreieckiges, und mit einer weiteren Erhöhung nimmt die Signalamplitude ab.
Erzeugung bei 1 kHz ( 90% Amplitude ):
Erzeugung bei 10 kHz ( 90% Amplitude ):
Erzeugung bei 100 kHz ( 90% Amplitude ):
Schritte sind bereits sichtbar, da neue Daten mit einer Frequenz von 4 MHz in den DAC geladen werden.
Außerdem ist die Hinterkante des Sägezahnsignals unübersichtlich und von unten erreicht das Signal nicht den Wert, auf den es sollte. Dies liegt daran, dass das Signal keine Zeit hat, den angegebenen niedrigen Pegel zu erreichen, und die Software neue Werte lädt
Erzeugung bei 200 kHz ( 90% Amplitude ):
Hier können Sie bereits sehen, wie sich alle Wellen in ein Dreieck verwandelt haben.
Digitale Kanäle
Mit digitalen Kanälen ist alles viel einfacher - in fast jedem Mikrocontroller gibt es Timer, mit denen Sie ein PWM-Signal an die Ausgänge des Mikrocontrollers ausgeben können. Verwenden Sie am besten einen 32-Bit-Timer. In diesem Fall müssen Sie den Timer-Pre-Timer nicht zählen. Laden Sie einfach die Periode in ein Register und laden Sie den erforderlichen Arbeitszyklus in ein anderes Register.
Benutzeroberfläche
Es wurde beschlossen, die Benutzeroberfläche in vier Rechtecke mit jeweils einem Bild des Ausgangssignals, der Frequenz und der Amplitude / des Arbeitszyklus zu organisieren. Für den aktuell ausgewählten Kanal werden die Textdaten in Weiß angezeigt, der Rest in Grau.

Es wurde beschlossen, die Encoder zu steuern: Der linke ist für die Frequenz und den aktuell ausgewählten Kanal verantwortlich (
ändert sich beim Drücken der Taste ), der rechte ist für die Amplitude / das Tastverhältnis und die Wellenform verantwortlich (
ändert sich beim Drücken der Taste ).
Darüber hinaus wird die Unterstützung für den Touchscreen implementiert. Wenn Sie auf einen inaktiven Kanal klicken, wird dieser aktiv. Wenn Sie auf einen aktiven Kanal klicken, ändert sich die Wellenform.
Natürlich wird DevCore verwendet, um all dies zu tun. Der Code zum Initialisieren der Benutzeroberfläche und zum Aktualisieren der Daten auf dem Bildschirm sieht folgendermaßen aus:
Struktur mit allen UI-Objekten Initialisierungscode der Benutzeroberfläche Bildschirmaktualisierungscode for(uint32_t i = 0U; i < CHANNEL_CNT; i++) { ch_dsc[i].img.SetImage(waveforms[ch_dsc[i].waveform]); snprintf(ch_dsc[i].freq_str_data, NumberOf(ch_dsc[i].freq_str_data), "Freq: %7lu Hz", ch_dsc[i].frequency); if(IsAnalogChannel(i)) snprintf(ch_dsc[i].duty_str_data, NumberOf(ch_dsc[i].duty_str_data), "Ampl: %7d %%", ch_dsc[i].duty); else snprintf(ch_dsc[i].duty_str_data, NumberOf(ch_dsc[i].duty_str_data), "Duty: %7d %%", ch_dsc[i].duty);
Eine interessante Implementierung des Schaltflächenklicks wird implementiert (es
ist ein Rechteck, über das die verbleibenden Elemente gezeichnet werden ). Wenn Sie sich den Code angesehen haben, sollten Sie
Folgendes bemerkt haben:
ch_dsc [i] .box.SetCallback (& Callback, this, nullptr, i); in einer Schleife aufgerufen. Dies ist die Aufgabe der Rückruffunktion, die beim Drücken der Taste aufgerufen wird. Folgendes wird an die Funktion übertragen: die Adresse der statischen Funktion der statischen Funktion der Klasse, der this-Zeiger und zwei Benutzerparameter, die an die Rückruffunktion übergeben werden - ein Zeiger (
in diesem Fall nicht verwendet - nullptr wird übergeben ) und eine Nummer (
Kanalnummer wird übertragen ).
Von der Universitätsbank aus erinnere ich mich an das Postulat: "
Statische Funktionen haben keinen Zugriff auf nicht statische Klassenmitglieder ." Das ist also
nicht wahr . Da eine statische Funktion Mitglied einer Klasse ist,
hat sie Zugriff auf alle Mitglieder der Klasse, wenn sie einen Link / Zeiger auf diese Klasse hat. Schauen Sie sich nun die Rückruffunktion an:
In der ersten Zeile dieser Funktion tritt "
Magie " auf. Danach können Sie auf alle Mitglieder der Klasse zugreifen, auch auf private.
Diese Funktion wird übrigens in einer anderen Aufgabe (
Rendern des Bildschirms ) aufgerufen. Innerhalb dieser Funktion müssen Sie sich also um die Synchronisation kümmern. In diesem einfachen "
paar Abende " -Projekt habe ich dies nicht getan, da es in diesem speziellen Fall nicht wesentlich ist.
Auf GitHub hochgeladener Generator-Quellcode:
https://github.com/nickshl/WaveformGeneratorDevCore ist jetzt einem separaten Repository zugeordnet und als Submodul enthalten.
Nun, warum brauche ich einen Signalgenerator, der im nächsten (
oder einem der folgenden ) Artikel beschrieben wird.