Girino - Schnelles Arduino-Oszilloskop

Ich bin Physiker und das Schönste an der Arbeit auf diesem Gebiet ist, dass ich meine eigenen Instrumente bauen kann. Mit dieser Denkweise beschloss ich, ein Homebrew-Arduino-Oszilloskop zu bauen. Diese Anleitung wurde mit dem Ziel geschrieben, ein wenig über Mikrocontroller und Datenerfassung zu lehren. Dies ist ein extremes Projekt, weil ich so viel Geschwindigkeit wie möglich aus Arduino herausdrücken wollte. Ich habe kein anderes Arduino-Oszilloskop so schnell gesehen wie dieses.

Vor einiger Zeit arbeitete ich an einem Arduino-Projekt und musste prüfen, ob das Ausgangssignal den Spezifikationen entsprach. Daher habe ich einige Zeit im Internet nach bereits implementierten Arduino-Oszilloskopen gesucht, aber mir hat nicht gefallen, was ich gefunden habe. Die Projekte, die ich gefunden habe, bestanden hauptsächlich aus einer grafischen Benutzeroberfläche für den in Processing geschriebenen Computer und einer sehr einfachen Arduino-Skizze. Die Skizzen waren ungefähr so: void setup () {

Serial.begin (9600);

}}

void loop () {

int val = analogRead (ANALOG_IN);

Serial.println (val);

} Dieser Ansatz ist nicht falsch und ich möchte niemanden beleidigen, aber das ist zu langsam für mich. Die serielle Schnittstelle ist langsam und das Senden jedes Ergebnisses eines analogRead () durch sie ist ein Engpass.

Ich habe einige Zeit Wellenform-Digitalisierer studiert und weiß ziemlich gut, wie sie funktionieren, also habe ich mich von ihnen inspirieren lassen. Dies waren die Ausgangspunkte des Oszilloskops, das ich erstellen wollte:

  • Das eingehende Signal sollte vom Arduino entkoppelt werden, um es zu erhalten.
  • mit einem Versatz des Signals ist es möglich, negative Signale zu sehen;
  • Die Daten sollten gepuffert werden.
  • Zum Abfangen der Signale ist ein Hardware-Trigger erforderlich.
  • Ein kreisförmiger Puffer kann die Signalform vor dem Trigger angeben (weitere folgen zu diesem Punkt).
  • Durch die Verwendung von Funktionen mit niedrigeren Hebeln als die Standardfunktionen wird das Programm schneller ausgeführt.

Die Skizze für das Arduino ist diesem Schritt zusammen mit dem Schema der von mir erstellten Schaltung beigefügt.

Der Name, den ich mir ausgedacht habe, Girino, ist ein leichtfertiges Wortspiel auf Italienisch. Giro bedeutet Rotation und das Hinzufügen des Suffix -ino ergibt eine kleine Rotation, aber Girino bedeutet auch Kaulquappe . Auf diese Weise bekam ich einen Namen und ein Maskottchen.

Schritt 1: Haftungsausschluss

DER AUTOR DIESER ANLEITUNG ÜBERNIMMT KEINE GARANTIE FÜR DIE GÜLTIGKEIT UND KEINE GEWÄHRLEISTUNG .

Elektronik kann gefährlich sein, wenn Sie nicht wissen, was Sie tun, und der Autor die Gültigkeit der hier enthaltenen Informationen nicht garantieren kann. Dies ist kein professioneller Rat und alles, was in dieser Anleitung geschrieben ist, kann ungenau, irreführend, gefährlich oder falsch sein. Verlassen Sie sich nicht auf Informationen, die hier ohne unabhängige Überprüfung gefunden wurden.

Es liegt an Ihnen, alle Informationen zu überprüfen und zu überprüfen, ob Sie sich selbst oder irgendjemandem Schaden zufügen oder irgendetwas einem Schaden aussetzen. Ich übernehme keine Verantwortung. Sie müssen selbst die richtigen Sicherheitsvorkehrungen treffen, wenn Sie dieses Projekt reproduzieren möchten.

Verwenden Sie diesen Leitfaden auf eigenes Risiko!

Schritt 2: Was Sie brauchen

Was wir für dieses Projekt wirklich brauchen, ist ein Arduino-Board und das Datenblatt des ATMega328P.
Das Datenblatt zeigt uns, wie der Mikrocontroller funktioniert, und es ist sehr wichtig, ihn beizubehalten, wenn wir einen niedrigeren Steuerhebel wünschen.

Das Datenblatt finden Sie hier: //www.atmel.com/Images/doc8271.pdf

Die Hardware, die ich dem Arduino hinzugefügt habe, ist teilweise notwendig. Sie dient lediglich dazu, das Signal für den ADC zu bilden und einen Spannungspegel für den Trigger bereitzustellen. Wenn Sie möchten, können Sie das Signal direkt an das Arduino senden und eine durch einen Spannungsteiler definierte Spannungsreferenz oder sogar die vom Arduino selbst angegebenen 3, 3 V verwenden.

Schritt 3: Debug-Ausgabe

Normalerweise füge ich viel Debug-Ausgabe in meine Programme ein, weil ich alles verfolgen möchte, was passiert. Das Problem mit Arduino ist, dass wir keinen Standard zum Schreiben haben. Ich habe mich entschieden, den seriellen Port als Standard zu verwenden.

Beachten Sie jedoch, dass dieser Ansatz nicht immer funktioniert! Da das Schreiben auf die serielle Schnittstelle einige Zeit für die Ausführung benötigt und sich während einiger Zeit sinnvoller Routinen dramatisch ändern kann.

Normalerweise definiere ich Debugging-Ausgaben in einem Präprozessor-Makro. Wenn das Debugging deaktiviert ist, verschwinden sie einfach aus dem Programm und verlangsamen die Ausführung nicht:
  • Druck (x); - Schreibt auf die serielle Schnittstelle wie folgt: # x: 123
  • dshow ("Some string"); - Schreibt den String

Dies ist die Definition:

#if DEBUG == 1
#define dprint (Ausdruck) Serial.print ("#"); Serial.print (#expression); Serial.print (":"); Serial.println (Ausdruck)
#define dshow (Ausdruck) Serial.println (Ausdruck)
#sonst
#define dprint (Ausdruck)
#define dshow (Ausdruck)
#endif

Schritt 4: Einstellen der Registerbits

Um schnell zu sein, müssen die Mikrocontrollerfunktionen mit niedrigeren Hebelfunktionen als den von der Arduino IDE bereitgestellten Standardfunktionen manipuliert werden. Die internen Funktionen werden über einige Register verwaltet, bei denen es sich um Sammlungen von acht Bits handelt, von denen jedes etwas Bestimmtes regelt. Jedes Register enthält acht Bits, da der ATMega328P über eine 8-Bit-Architektur verfügt.

Die Register haben einige Namen, die abhängig von ihrer Bedeutung im Datenblatt angegeben sind, wie z. B. ADCSRA für das ADC-Einstellungsregister A. Außerdem hat jedes aussagekräftige Bit der Register einen Namen, wie z. B. ADEN für das ADC-Aktivierungsbit im ADCSRA-Register.

Um ihre Bits zu setzen, könnten wir die übliche C-Syntax für die binäre Algebra verwenden, aber ich habe im Internet ein paar Makros gefunden, die sehr schön und sauber sind:

// Definiert zum Setzen und Löschen von Registerbits
#ifndef cbi
#define cbi (sfr, bit) (_SFR_BYTE (sfr) & = ~ _BV (bit))
#endif
#ifndef sbi
#define sbi (sfr, bit) (_SFR_BYTE (sfr) | = _BV (bit))
#endif

Ihre Verwendung ist sehr einfach. Wenn wir das Aktivierungsbit des ADC auf 1 setzen möchten, können wir einfach schreiben:

sbi (ADCSRA, ADEN);

Wenn wir es auf 0 setzen wollen ( id est clear it), können wir einfach schreiben:

cbi (ADCSRA, ADEN);

Schritt 5: Was sind die Interrupts?

Wie wir in den nächsten Schritten sehen werden, ist in diesem Projekt die Verwendung von Interrupts erforderlich. Interrupts sind Signale, die den Mikrocontroller anweisen, die Ausführung der Hauptschleife zu stoppen und an einige Sonderfunktionen weiterzuleiten. Die Bilder geben einen Eindruck vom Programmablauf.

Die ausgeführten Funktionen werden als Interrupt Service Routines (ISR) bezeichnet und sind mehr oder weniger einfache Funktionen, die jedoch keine Argumente enthalten.

Sehen wir uns ein Beispiel an, etwa das Zählen einiger Impulse. Der ATMega328P verfügt über einen Analogkomparator, dem ein Interrupt zugeordnet ist, der aktiviert wird, wenn ein Signal eine Referenzspannung überschreitet. Zunächst müssen Sie die Funktion definieren, die ausgeführt werden soll:

ISR (ANALOG_COMP_vect)
{
Zähler ++;
}}

Dies ist wirklich einfach. Die Anweisung ISR () ist ein Makro, das dem Compiler mitteilt, dass die folgende Funktion eine Interrupt-Serviceroutine ist. Während ANALOG_COMP_vect Interrupt Vector heißt und dem Compiler mitteilt, welcher Interrupt dieser Routine zugeordnet ist. In diesem Fall handelt es sich um den Analogkomparator-Interrupt. Jedes Mal, wenn der Komparator ein Signal sieht, das größer als eine Referenz ist, weist er den Mikrocontroller an, diesen Code auszuführen. In diesem Fall muss diese Variable erhöht werden.

Der nächste Schritt besteht darin, den zugehörigen Interrupt zu aktivieren. Um es zu aktivieren, müssen wir das ACIE-Bit (Analog Comparator Interrupt Enable) des ACSR-Registers (Analog Comparator Setting Register) setzen:

sbi (ACSR, ACIE);

Auf der folgenden Seite sehen wir die Liste aller Interrupt-Vektoren:
//www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html

Schritt 6: Kontinuierlich mit einem kreisförmigen Puffer erfassen

Das Konzept der Verwendung eines Kreispuffers ist ziemlich einfach:

Erfassen Sie kontinuierlich, bis ein Signal gefunden wurde, und senden Sie das digitalisierte Signal an den Computer.

Dieser Ansatz ermöglicht es, die eingehende Signalform auch vor dem Triggerereignis zu haben.


Ich habe einige Diagramme vorbereitet, um mich klar zu machen. Die folgenden Punkte beziehen sich auf die Bilder.
  • Auf dem ersten Bild können wir sehen, was ich mit kontinuierlicher Erfassung meine. Wir definieren einen Puffer, der die Daten speichert, in meinem Fall ein Array mit 1280 Slots, und beginnen dann, das ADC-Ausgangsregister (ADCH) kontinuierlich zu lesen und den Puffer mit den Daten zu füllen. Wenn wir am Ende des Puffers angelangt sind, starten wir von Anfang an neu, ohne ihn zu löschen. Wenn wir uns das kreisförmig angeordnete Array vorstellen, ist es leicht zu verstehen, was ich meine.
  • Wenn das Signal den Schwellenwert überschreitet, wird der Analogkomparator-Interrupt aktiviert. Dann starten wir eine Wartephase, in der wir das Signal weiter erfassen, aber die ADC-Zyklen zählen, die vom Analogkomparator-Interrupt vergangen sind.
  • Wenn wir auf N Zyklen gewartet haben (mit N <1280), frieren wir die Situation ein und stoppen die ADC-Zyklen. Wir erhalten also einen Puffer, der mit der Digitalisierung der zeitlichen Signalform gefüllt ist. Der große Teil davon ist, dass wir auch die Form vor dem Auslöseereignis haben, weil wir bereits vorher erworben haben.
  • Jetzt können wir den gesamten Puffer in einem Block von Binärdaten an die serielle Schnittstelle senden, anstatt die einzelnen ADC-Lesevorgänge zu senden. Dies reduzierte den Aufwand für das Senden der Daten und den Engpass der Skizzen, die ich im Internet gefunden habe.

Schritt 7: Oszilloskop-Triggerung

Ein Oszilloskop zeigt auf seinem Display ein Signal an, dem wir uns alle einig sind, aber wie kann es es stetig anzeigen und nicht über den Bildschirm springen lassen? Es verfügt über einen internen Trigger, der das Signal immer an derselben Position des Bildschirms (oder zumindest meistens) anzeigen kann, wodurch die Illusion eines stabilen Diagramms entsteht.

Der Trigger ist einem Schwellenwert zugeordnet, der einen Sweep aktiviert , wenn das Signal ihn passiert. Ein Sweep ist die Phase, in der das Oszilloskop das Signal aufzeichnet und anzeigt. Nach einem Sweep tritt eine weitere Phase auf: das Holdoff, bei dem das Oszilloskop ein eingehendes Signal zurückweist. Die Halteperiode kann sich aus einem Teil der Totzeit, in der das Oszilloskop kein Signal empfangen kann, und einem Teil zusammensetzen, der vom Benutzer ausgewählt werden kann. Die Totzeit kann durch verschiedene Gründe verursacht werden, z. B. durch Zeichnen auf dem Bildschirm oder Speichern der Daten irgendwo.

Wenn wir uns das Bild ansehen, bekommen wir ein Gefühl dafür, was passiert.
  1. Signal 1 überschreitet die Schwelle und aktiviert den Sweep.
  2. Signal 2 liegt innerhalb der Sweep-Zeit und wird mit dem ersten abgefangen;
  3. Nach dem Holdoff aktiviert Signal 3 den Sweep erneut.
  4. stattdessen wird Signal 4 zurückgewiesen, weil es in den Haltebereich fällt.
Das Ziel der Holdoff-Phase besteht darin, zu verhindern, dass unerwünschte Signale in den Sweep-Bereich gelangen. Es ist ein bisschen lang, diesen Punkt zu erklären, und es entzieht sich dem Zweck dieser Anleitung.

Die Moral dieser Geschichte ist, dass wir brauchen:
  1. einen Schwellenwert, mit dem wir das eingehende Signal vergleichen können;
  2. Ein Signal, das den Mikrocontroller anweist, die Wartephase zu starten (siehe vorherigen Schritt).
Wir haben mehrere mögliche Lösungen für Punkt 1 .:
  • Mit einem Trimmer können wir manuell einen Spannungspegel einstellen.
  • Mit der PWM des Arduino können wir den Pegel per Software einstellen.
  • unter Verwendung der vom Arduino selbst bereitgestellten 3, 3 V;
  • Mit der internen Bangap-Referenz können wir einen festen Pegel verwenden.
Für Punkt 2 haben wir die richtige Lösung: Wir können den Interrupt des internen Analogkomparators des Mikrocontrollers verwenden.

Schritt 8: Wie der ADC funktioniert

Der Arduino-Mikrocontroller verfügt über einen einzelnen ADC mit sukzessiver 10-Bit-Approximation. Vor dem ADC gibt es einen analogen Multiplexer, mit dem wir die Signale von verschiedenen Pins und Quellen (aber jeweils nur von einem) an den ADC senden können.

ADC mit sukzessiver Approximation bedeutet, dass der ADC 13 Taktzyklen benötigt, um die Konvertierung abzuschließen (und 25 Taktzyklen für die erste Konvertierung). Dem ADC ist ein Taktsignal zugeordnet, das aus dem Haupttakt des Arduino "berechnet" wird. Dies liegt daran, dass der ADC etwas langsam ist und nicht mit dem Tempo der anderen Teile des Mikrocontrollers mithalten kann. Für eine maximale Auflösung ist eine Eingangstaktfrequenz zwischen 50 kHz und 200 kHz erforderlich. Wenn eine niedrigere Auflösung als 10 Bit benötigt wird, kann die Eingangstaktfrequenz des ADC höher als 200 kHz sein, um eine höhere Abtastrate zu erhalten.

Aber wie viel höhere Raten können wir verwenden? Es gibt ein paar gute Anleitungen zum ADC in den Open Music Labs, die ich lesen möchte:
  • //www.openmusiclabs.com/learning/digital/atmega-adc/
  • //www.openmusiclabs.com/learning/digital/atmega-adc/in-depth/
Da mein Ziel darin besteht, ein schnelles Oszilloskop zu erhalten, habe ich beschlossen, die Genauigkeit auf 8 Bit zu beschränken. Dies hat mehrere Boni:
  1. Der Datenpuffer kann mehr Daten speichern.
  2. Sie verschwenden keine 6-Bit-RAM pro Datum.
  3. Der ADC kann schneller erfassen.
Mit dem Prescaler können wir die Frequenz durch einige Faktoren teilen, indem wir die ADPS0-1-2-Bits des ADCSRA-Registers setzen. Wenn wir uns die Darstellung der Genauigkeit aus dem Open Music Labs-Artikel ansehen, können wir sehen, dass bei einer Genauigkeit von 8 Bit die Frequenz bis zu 1, 5 MHz betragen kann, gut! Da wir durch Ändern des Prescaler-Faktors die Erfassungsrate ändern können, können wir damit auch die Zeitskala des Oszilloskops ändern.

Die Ausgangsregister haben eine gute Eigenschaft: Wir können die Anpassung der Konvertierungsbits durch Setzen des ADLAR-Bits im ADMUX-Register bestimmen. Wenn es 0 ist, sind sie richtig eingestellt und umgekehrt (siehe Bild). Da ich eine Genauigkeit von 8 Bit wollte, setzte ich sie auf 1, damit ich nur das ADCH-Register lesen und die ADCL ignorieren konnte.

Ich entschied mich für nur einen Eingangskanal, um zu vermeiden, dass bei jeder Konvertierung der Kanal hin und her gewechselt werden muss.

Eine letzte Sache am ADC ist, dass er unterschiedliche Betriebsmodi mit jeweils unterschiedlicher Triggerquelle hat:
  • Freilaufmodus
  • Analoger Komparator
  • Externe Interrupt-Anforderung 0
  • Timer / Counter0 Vergleiche Match A.
  • Timer / Zähler0 Überlauf
  • Timer / Zähler1 Vergleiche Match B.
  • Timer / Zähler1 Überlauf
  • Timer / Counter1 Capture-Ereignis
Ich war an dem Freilaufmodus interessiert, bei dem der ADC den Eingang kontinuierlich konvertiert und am Ende jeder Konvertierung einen Interrupt auslöst (zugehöriger Vektor: ADC_vect).

Schritt 9: Digitale Eingangspuffer

Die analogen Eingangspins des Arduino können auch als digitale E / A-Pins verwendet werden, daher verfügen sie über einen Eingangspuffer für digitale Funktionen. Wenn wir sie als analoge Pins verwenden möchten, sollten Sie diese Funktion deaktivieren.

Durch das Senden eines analogen Signals an einen digitalen Pin wird zwischen dem Zustand HIGH und LOW umgeschaltet, insbesondere wenn sich das Signal nahe der Grenze zwischen den beiden Zuständen befindet. Dieses Umschalten führt zu Rauschen in den nahen Schaltkreisen wie dem ADC selbst (und zu einem höheren Energieverbrauch).

Um den digitalen Puffer zu deaktivieren, sollten wir die ADCnD-Bits des DIDR0-Registers setzen:

sbi (DIDR0, ADC5D);
sbi (DIDR0, ADC4D);
sbi (DIDR0, ADC3D);
sbi (DIDR0, ADC2D);
sbi (DIDR0, ADC1D);
sbi (DIDR0, ADC0D);

Schritt 10: Einrichten des ADC

In der Skizze habe ich eine Initialisierungsfunktion geschrieben, die alle Parameter der ADC-Funktion einstellt. Da ich dazu neige, sauberen und kommentierten Code zu schreiben, werde ich die Funktion hier einfach hinter mir lassen. Wir können uns auf den vorhergehenden Schritt und auf die Kommentare zur Bedeutung der Register beziehen. void initADC (void)
= (ADCPIN & 0x07);

// ------------------------------------------------ ---------------------
// ADCSRA-Einstellungen
// ------------------------------------------------ ---------------------
// Das Schreiben dieses Bits in eins aktiviert den ADC. Indem Sie es auf Null schreiben, wird die
// ADC ist ausgeschaltet. Ausschalten des ADC während einer Konvertierung
// Fortschritt, beendet diese Konvertierung.
cbi (ADCSRA, ADEN);
// Schreiben Sie im Einzelkonvertierungsmodus dieses Bit in eines, um jedes zu starten
// Konvertierung. Schreiben Sie im Freilaufmodus dieses Bit in eins, um das zu starten
// erste Konvertierung. Die erste Konvertierung nach ADSC wurde geschrieben
// nachdem der ADC aktiviert wurde oder wenn ADSC gleichzeitig geschrieben wurde
// Wenn der ADC aktiviert ist, werden stattdessen 25 ADC-Taktzyklen benötigt
// die normale 13. Diese erste Konvertierung führt die Initialisierung der
// ADC. ADSC liest als eins, solange eine Konvertierung ausgeführt wird.
// Wenn die Konvertierung abgeschlossen ist, kehrt sie auf Null zurück. Null schreiben an
// Dieses Bit hat keine Auswirkung.
cbi (ADCSRA, ADSC);
// Wenn dieses Bit in eins geschrieben wird, ist die automatische Auslösung des ADC
// aktiviert. Der ADC startet eine Konvertierung an einer positiven Flanke des
// ausgewähltes Triggersignal. Die Triggerquelle wird durch Einstellung ausgewählt
// der ADC-Trigger Wählen Sie die Bits ADTS in ADCSRB aus.
sbi (ADCSRA, ADATE);
// Wenn dieses Bit in eins geschrieben wird und das I-Bit in SREG gesetzt ist, wird das
// ADC Conversion Complete Interrupt ist aktiviert.
sbi (ADCSRA, ADIE);
// Diese Bits bestimmen den Teilungsfaktor zwischen der Systemuhr
// Frequenz und Eingangstakt zum ADC.
// ADPS2 ADPS1 ADPS0 Divisionsfaktor
// 0 0 0 2
// 0 0 1 2
// 0 1 0 4
// 0 1 1 8
// 1 0 0 16
// 1 0 1 32
// 1 1 0 64
// 1 1 1 128
sbi (ADCSRA, ADPS2);
sbi (ADCSRA, ADPS1);
sbi (ADCSRA, ADPS0);

// ------------------------------------------------ ---------------------
// ADCSRB-Einstellungen
// ------------------------------------------------ ---------------------
// Wenn dieses Bit logisch eins geschrieben ist und der ADC ausgeschaltet ist
// (ADEN in ADCSRA ist Null), der ADC-Multiplexer wählt das Negativ aus
// Eingang zum Analogkomparator. Wenn dieses Bit logisch Null geschrieben ist,
// AIN1 wird an den negativen Eingang des Analogkomparators angelegt.
cbi (ADCSRB, ACME);
// Wenn ADATE in ADCSRA in eins geschrieben wird, der Wert dieser Bits
// wählt aus, welche Quelle eine ADC-Konvertierung auslöst. Wenn ADATE ist
// gelöscht, haben die ADTS2: 0-Einstellungen keine Auswirkung. Eine Umstellung wird
// wird durch die ansteigende Flanke des ausgewählten Interrupt-Flags ausgelöst. Hinweis
// das Umschalten von einer Triggerquelle, die gelöscht wurde, auf einen Trigger
// Quelle, die gesetzt ist, erzeugt eine positive Flanke am Trigger
// Signal. Wenn ADEN in ADCSRA festgelegt ist, wird eine Konvertierung gestartet.
// Das Umschalten in den Freilaufmodus (ADTS [2: 0] = 0) führt nicht zu a
// Ereignis auslösen, auch wenn das ADC-Interrupt-Flag gesetzt ist.
// ADTS2 ADTS1 ADTS0 Triggerquelle
// 0 0 0 Freilaufmodus
// 0 0 1 Analogkomparator
// 0 1 0 Externe Interrupt-Anforderung 0
// 0 1 1 Timer / Counter0 Vergleiche Match A.
// 1 0 0 Timer / Zähler0 Überlauf
// 1 0 1 Timer / Zähler1 Vergleiche Match B.
// 1 1 0 Timer / Zähler1 Überlauf
// 1 1 1 Timer / Counter1 Capture Event
cbi (ADCSRB, ADTS2);
cbi (ADCSRB, ADTS1);
cbi (ADCSRB, ADTS0);

// ------------------------------------------------ ---------------------
// DIDR0-Einstellungen
// ------------------------------------------------ ---------------------
// Wenn dieses Bit logisch eins geschrieben ist, wird der digitale Eingangspuffer auf dem
// entsprechender ADC-Pin ist deaktiviert. Das entsprechende PIN-Register
// Bit wird immer als Null gelesen, wenn dieses Bit gesetzt ist. Wenn ein Analog
// Signal wird an den ADC5..0 Pin und den digitalen Eingang von diesem angelegt
// Pin wird nicht benötigt, dieses Bit sollte logisch eins geschrieben werden, um es zu reduzieren
// Stromverbrauch im digitalen Eingangspuffer.
// Beachten Sie, dass die ADC-Pins ADC7 und ADC6 keine digitalen Eingangspuffer haben.
// und benötigen daher keine Digital Input Disable-Bits.
sbi (DIDR0, ADC5D);
sbi (DIDR0, ADC4D);
sbi (DIDR0, ADC3D);
sbi (DIDR0, ADC2D);
sbi (DIDR0, ADC1D);
sbi (DIDR0, ADC0D);

Schritt 11: Funktionsweise des Analogkomparators

Der Analogkomparator ist ein internes Modul des Mikrocontrollers und vergleicht die Eingangswerte am positiven Pin (digitaler Pin 6) und negativen Minus (digitaler Pin 7). Wenn die Spannung am positiven Pin höher ist als die Spannung am negativen Pin AIN1, gibt der Analogkomparator eine 1 im ACO-Bit des ACSR-Registers aus.

Optional kann der Komparator einen Interrupt auslösen, der ausschließlich für den Analogkomparator gilt. Der zugehörige Vektor ist ANALOG_COMP_vect.

Wir können den Interrupt auch so einstellen, dass er an einer steigenden Flanke, einer fallenden Flanke oder an einem Umschalter des Zustands gestartet wird.

Der Analogkomparator ist genau das, was wir für die Auslösung benötigen, um das Eingangssignal an Pin 6 anzuschließen. Jetzt bleibt nur noch ein Schwellenwert an Pin 7.

Schritt 12: Einrichten des Analogkomparators

In der Skizze habe ich eine weitere Initialisierungsfunktion geschrieben, mit der alle Parameter der Funktion des Analogkomparators eingerichtet werden. Das gleiche Problem mit ADC-Digitalpuffern gilt für den Analogkomparator, wie wir am Ende der Routine sehen können.

void initAnalogComparator (void)
{
// ------------------------------------------------ ---------------------
// ACSR-Einstellungen
// ------------------------------------------------ ---------------------
// Wenn dieses Bit logisch eins geschrieben ist, wird die Leistung an das Analog angeschlossen
// Komparator ist ausgeschaltet. Dieses Bit kann jederzeit zum Drehen gesetzt werden
// aus dem Analogkomparator. Dies reduziert den Stromverbrauch in
// Aktiv- und Leerlaufmodus. Beim Ändern des ACD-Bits wird das Analog
// Der Komparator-Interrupt muss durch Löschen des ACIE-Bits deaktiviert werden
// ACSR. Andernfalls kann ein Interrupt auftreten, wenn das Bit geändert wird.
cbi (ACSR, ACD);
// Wenn dieses Bit gesetzt ist, ersetzt eine Referenzspannung mit fester Bandlücke die
// positiver Eingang zum Analogkomparator. Wenn dieses Bit gelöscht ist,
// AIN0 wird an den positiven Eingang des Analogkomparators angelegt. Wann
// Die Bandlückenreferenz wird als Eingabe für den Analogkomparator verwendet
// Es wird eine gewisse Zeit dauern, bis sich die Spannung stabilisiert hat. Wenn nicht
// stabilisiert, kann die erste Konvertierung einen falschen Wert ergeben.
cbi (ACSR, ACBG);
// Wenn das ACIE-Bit als logisch eins und das I-Bit im Status geschrieben ist
// Register wird gesetzt, der Analogkomparator-Interrupt ist aktiviert.
// Wenn logische Null geschrieben wird, ist der Interrupt deaktiviert.
cbi (ACSR, ACIE);
// Wenn eine logische Eins geschrieben ist, aktiviert dieses Bit die Eingabeerfassungsfunktion
// in Timer / Counter1, der vom Analogkomparator ausgelöst werden soll. Das
// Der Komparatorausgang ist in diesem Fall direkt mit dem Eingang verbunden
// Front-End-Logik erfassen, sodass der Komparator das Rauschen nutzt
// Canceller- und Flankenauswahlfunktionen des Timer / Counter1-Eingangs
// Interrupt erfassen. Bei geschriebener logischer Null keine Verbindung zwischen
// Der Analogkomparator und die Eingangserfassungsfunktion sind vorhanden. Zu
// Lassen Sie den Komparator die Timer / Counter1 Input Capture auslösen
// Interrupt, das ICIE1-Bit im Timer Interrupt Mask Register
// (TIMSK1) muss gesetzt sein.
cbi (ACSR, ACIC);
// Diese Bits bestimmen, welche Komparatorereignisse das Analog auslösen
// Komparator-Interrupt.
// ACIS1 ACIS0-Modus
// 0 0 Umschalten
// 0 1 Reserviert
// 1 0 Fallende Flanke
// 1 1 Steigende Flanke
sbi (ACSR, ACIS1);
sbi (ACSR, ACIS0);

// ------------------------------------------------ ---------------------
// DIDR1-Einstellungen
// ------------------------------------------------ ---------------------
// Wenn dieses Bit logisch eins geschrieben ist, wird der digitale Eingangspuffer auf dem
// AIN1 / 0 Pin ist deaktiviert. Das entsprechende PIN-Registerbit wird
// immer als Null lesen, wenn dieses Bit gesetzt ist. Wenn ein analoges Signal ist
// an den AIN1 / 0-Pin angelegt und der digitale Eingang von diesem Pin nicht
// benötigt, sollte dieses Bit logisch eins geschrieben sein, um die Leistung zu reduzieren
// Verbrauch im digitalen Eingangspuffer.
sbi (DIDR1, AIN1D);
sbi (DIDR1, AIN0D);
}}

Schritt 13: Schwelle

Wenn wir uns an das erinnern, was wir über den Auslöser gesagt haben, können wir diese beiden Lösungen für den Schwellenwert implementieren:
  • Mit einem Trimmer können wir manuell einen Spannungspegel einstellen.
  • Mit der PWM des Arduino können wir den Pegel per Software einstellen.
Auf dem Bild sehen wir die Hardware-Implementierung des Schwellenwerts in beiden Pfaden.

Für die manuelle Auswahl ist ein Multi-Turn-Potentiometer zwischen +5 V und GND ausreichend.

Während wir für die Softwareauswahl einen Tiefpassfilter benötigen, der ein vom Arduino kommendes PWM-Signal filtert. PWM-Signale (mehr dazu folgt) sind quadratische Signale mit einer konstanten Frequenz, aber einer variablen Impulsbreite. Diese Variabilität bringt einen variablen Mittelwert des Signals mit sich, der mit einem Tiefpassfilter extrahiert werden kann. Eine gute Grenzfrequenz für das Filter beträgt ungefähr ein Hundertstel der PWM-Frequenz, und ich habe ungefähr 560 Hz gewählt.

Nach den beiden Schwellenwertquellen habe ich ein paar Pins eingefügt, mit denen ich mit einem Jumper auswählen kann, welche Quelle ich haben möchte. Nach der Auswahl habe ich auch einen Emitterfolger hinzugefügt, um die Quellen vom Arduino-Pin zu entkoppeln.

Schritt 14: Funktionsweise der Pulsweitenmodulation

Wie bereits erwähnt, ist ein PWM-Signal (Pulse Width Modulation) ein quadratisches Signal mit fester Frequenz, aber variabler Breite. Auf dem Bild sehen wir ein Beispiel. In jeder Zeile gibt es eines dieser Signale mit einem anderen Arbeitszyklus ( id den Periodenabschnitt, in dem das Signal hoch ist). Wenn wir das mittlere Signal über einen Zeitraum nehmen, erhalten wir die rote Linie, die dem Arbeitszyklus in Bezug auf das Signalmaximum entspricht.

Elektronisch kann "Mittelwert eines Signals nehmen" in "Weiterleiten an ein Tiefpassfilter" übersetzt werden, wie im vorhergehenden Schritt gezeigt.

Wie erzeugt der Arduino ein PWM-Signal? Hier gibt es ein wirklich gutes Tutorial über PWM:
//arduino.cc/en/Tutorial/SecretsOfArduinoPWM
Wir werden nur die Punkte sehen, die für dieses Projekt benötigt werden.

Im ATMega328P gibt es drei Timer, mit denen PWM-Signale erzeugt werden können. Jeder dieser Timer weist unterschiedliche Eigenschaften auf, die Sie verwenden können. Für jeden Timer entsprechen zwei Register, die als Ausgangsvergleichsregister A / B (OCRnx) bezeichnet werden und zum Einstellen des Signal-Arbeitszyklus verwendet werden.

Für den ADC gibt es einen Prescaler (siehe Bild), der den Haupttakt verlangsamt, um die PWM-Frequenz präzise zu steuern. Der verlangsamte Takt wird einem Zähler zugeführt, der ein Timer / Zählerregister (TCNTn) inkrementiert. Dieses Register wird kontinuierlich mit dem OCRnx verglichen, wenn sie gleich sind, wird ein Signal an einen Wellenformgenerator gesendet, der einen Impuls am Ausgangspin erzeugt. Der Trick besteht also darin, das OCRnx-Register auf einen bestimmten Wert zu setzen, um den Mittelwert des Signals zu ändern.

Wenn wir ein 5-V-Signal (maximal) wollen, müssen wir ein Tastverhältnis von 100% oder 255 im OCRnx einstellen (maximal für eine 8-Bit-Zahl), während wir, wenn wir ein Signal von 0, 5 V wollen, ein Tastverhältnis von 10% einstellen müssen oder eine 25 im OCRnx.

Da der Takt das TCNTn-Register füllen muss, bevor er für einen neuen Impuls von vorne beginnt, beträgt die Ausgangsfrequenz der PWM:

f = (Hauptuhr) / Vorteiler / (maximal TCNTn)

Beispiel für den Timer 0 und 2 (8 Bit) ohne Vorteiler: 16 MHz / 256 = 62, 5 kHz, für Timer 1 (16 Bit) 16 MHz / 65536 = 244 Hz.

Ich habe mich für den Timer Nummer 2 entschieden, weil
  • Der Timer 0 wird intern von der Arduino IDE für Funktionen wie millis () verwendet.
  • Timer 1 hat eine zu langsame Ausgangsfrequenz, da es sich um einen 16-Bit-Timer handelt.

Im ATMega328P gibt es verschiedene Arten von Betriebsarten der Timer, aber ich wollte die schnelle PWM ohne Vorskalierung, um die maximal mögliche Ausgangsfrequenz zu erhalten.

Schritt 15: Einrichten der PWM

In der Skizze habe ich eine weitere Initialisierungsfunktion geschrieben, die alle Parameter der Timer-Funktion einstellt und einige Pins initialisiert. void initPins (void)
{
// ------------------------------------------------ ---------------------
// TCCR2A-Einstellungen
// ------------------------------------------------ ---------------------
// Diese Bits steuern das Verhalten des Output Compare Pin (OC2A). Wenn einer oder
// Beide COM2A1: 0-Bits sind gesetzt, der OC2A-Ausgang überschreibt die
// normale Portfunktionalität des E / A-Pins, mit dem es verbunden ist.
// Beachten Sie jedoch, dass das DDR-Bit (Data Direction Register) verwendet wird
// entsprechend dem OC2A-Pin muss gesetzt sein, um das zu aktivieren
// Treiber ausgeben.
// Wenn OC2A an den Pin angeschlossen ist, ist die Funktion der COM2A1: 0 Bits
// hängt von der WGM22: 0-Bit-Einstellung ab.
// //
// Schneller PWM-Modus
// COM2A1 COM2A0
// 0 0 Normaler Portbetrieb, OC2A getrennt.
// 0 1 WGM22 = 0: Normaler Portbetrieb, OC0A getrennt.
// WGM22 = 1: OC2A beim Vergleichen der Übereinstimmung umschalten.
// 1 0 OC2A bei Compare Match löschen, OC2A auf BOTTOM setzen
// 1 1 Löschen Sie OC2A bei Compare Match, löschen Sie OC2A bei BOTTOM
cbi (TCCR2A, COM2A1);
cbi (TCCR2A, COM2A0);
sbi (TCCR2A, COM2B1);
cbi (TCCR2A, COM2B0);

// Kombiniert mit dem im TCCR2B-Register gefundenen WGM22-Bit diese Bits
// steuere die Zählfolge des Zählers, der Quelle für Maximum
// (TOP) Zählerwert und welche Art der Wellenformgenerierung verwendet werden soll
// Von der Timer / Counter-Einheit unterstützte Betriebsmodi sind:
// - Normalmodus (Zähler),
// - Timer im CTC-Modus (Compare Match) löschen,
// - zwei Arten von PWM-Modi (Pulse Width Modulation).
// //
// Modus WGM22 WGM21 WGM20 Operation TOP
// 0 0 0 0 Normal 0xFF
// 1 0 0 1 PWM 0xFF
// 2 0 1 0 CTC OCRA
// 3 0 1 1 Schnelle PWM 0xFF
// 4 1 0 0 Reserviert -
// 5 1 0 1 PWM OCRA
// 6 1 1 0 Reserviert -
// 7 1 1 1 Schnelle PWM OCRA
cbi (TCCR2B, WGM22);
sbi (TCCR2A, WGM21);
sbi (TCCR2A, WGM20);

// ------------------------------------------------ ---------------------
// TCCR2B-Einstellungen
// ------------------------------------------------ ---------------------
// Das FOC2A-Bit ist nur aktiv, wenn die WGM-Bits eine Nicht-PWM angeben
// Modus.
// Um ​​jedoch die Kompatibilität mit zukünftigen Geräten sicherzustellen, dieses Bit
// muss auf Null gesetzt werden, wenn TCCR2B geschrieben wird, wenn in PWM gearbeitet wird
// Modus. Wenn Sie eine logische Eins in das FOC2A-Bit schreiben, eine sofortige
// Compare Match wird der Waveform Generation-Einheit aufgezwungen. Die OC2A
// Die Ausgabe wird gemäß der Einstellung COM2A1: 0 Bit geändert. Beachten Sie, dass
// Das FOC2A-Bit ist als Strobe implementiert. Daher ist es der Wert
// in den COM2A1: 0-Bits vorhanden, die die Wirkung von bestimmen
// Vergleich erzwingen.
// Ein FOC2A-Strobe erzeugt keinen Interrupt und wird auch nicht gelöscht
// der Timer im CTC-Modus mit OCR2A als TOP.
// Das FOC2A-Bit wird immer als Null gelesen.
cbi (TCCR2B, FOC2A);
cbi (TCCR2B, FOC2B);

// Die drei Taktauswahlbits wählen die Taktquelle aus, die verwendet werden soll
// der Timer / Zähler.
// CS22 CS21 CS20 Prescaler
// 0 0 0 Keine Taktquelle (Timer / Zähler gestoppt).
// 0 0 1 Keine Vorskalierung
// 0 1 0 8
// 0 1 1 32
// 1 0 0 64
// 1 0 1 128
// 1 1 0 256
// 1 1 1 1024
cbi (TCCR2B, CS22);
cbi (TCCR2B, CS21);
sbi (TCCR2B, CS20);

pinMode (errorPin, OUTPUT);
PinMode (ThresholdPin, OUTPUT);

analogWrite (ThresholdPin, 127);
}}

Schritt 16: Flüchtige Variablen

Ich kann mich nicht erinnern, wo, aber ich habe gelesen, dass Variablen, die innerhalb eines ISR geändert werden, als flüchtig deklariert werden sollten.

Flüchtige Variablen sind Variablen, die sich im Laufe der Zeit ändern können, auch wenn das ausgeführte Programm sie nicht ändert. Genau wie Arduino-Register, die den Wert einiger externer Interventionen ändern können.

Warum möchte der Compiler über solche Variablen Bescheid wissen? Das liegt daran, dass der Compiler immer versucht, den von uns geschriebenen Code zu optimieren, um ihn schneller zu machen, und ihn ein wenig ändert, um seine Bedeutung nicht zu ändern. Wenn sich eine Variable von selbst ändert, könnte es dem Compiler so erscheinen, als würde sie während der Ausführung beispielsweise einer Schleife niemals geändert und ignoriert. Dabei kann es entscheidend sein, dass die Variable ihren Wert ändert. Durch das Deklarieren flüchtiger Variablen wird verhindert, dass der Compiler den entsprechenden Code ändert.

Für weitere Informationen empfehle ich, die Wikipedia-Seite zu lesen: //en.wikipedia.org/wiki/Volatile_variable

Schritt 17: Schreiben des Kernels der Skizze

Endlich sind wir beim Kernel des Programms angelangt!

Wie wir zuvor gesehen haben, wollte ich eine kontinuierliche Erfassung und schrieb die ADC Interrupt Service Routine, um die Daten kontinuierlich im Ringpuffer zu speichern. Es stoppt immer dann, wenn es den Index erreicht, der stopIndex entspricht. Der Puffer wird unter Verwendung des Modulo-Operators als kreisförmig implementiert.

// ------------------------------------------------ -----------------------------
// ADC-Konvertierung abgeschlossen Interrupt
// ------------------------------------------------ -----------------------------
ISR (ADC_vect)
{
// Wenn ADCL gelesen wird, wird das ADC-Datenregister erst nach ADCH aktualisiert
// ist gelesen. Folglich, wenn das Ergebnis angepasst bleibt und nicht mehr
// Wenn eine Genauigkeit von 8 Bit erforderlich ist, reicht es aus, ADCH zu lesen.
// Andernfalls muss zuerst ADCL und dann ADCH gelesen werden.
ADCBuffer [ADCCounter] = ADCH;

ADCCounter = (ADCCounter + 1)% ADCBUFFERSIZE;

if (warte)
{
if (stopIndex == ADCCounter)
{
// Situation einfrieren
// Deaktiviere ADC und stoppe den Free Running Conversion Mode
cbi (ADCSRA, ADEN);

einfrieren = wahr;
}}
}}
}}

Die Analog Comparator Interrupt Service Routine (die aufgerufen wird, wenn ein Signal den Schwellenwert überschreitet) deaktiviert sich selbst und weist den ADC ISR an, die Wartephase zu starten und den stopIndex festzulegen.

// ------------------------------------------------ -----------------------------
// Analogkomparator-Interrupt
// ------------------------------------------------ -----------------------------
ISR (ANALOG_COMP_vect)
{
// Analogkomparator-Interrupt deaktivieren
cbi (ACSR, ACIE);

// errorPin einschalten
// digitalWrite (errorPin, HIGH);
sbi (PORTB, PORTB5);

wait = true;
stopIndex = (ADCCounter + waitDuration)% ADCBUFFERSIZE;
}}


Das war nach all der Erdung wirklich einfach!

Schritt 18: Eingehendes Signal bilden

Kommen wir jetzt zur Hardware. Die Schaltung mag kompliziert aussehen, ist aber sehr einfach.
  • Am Eingang befindet sich ein 1 MΩ-Widerstand, der eine Massenreferenz für das Signal liefert und einen hochohmigen Eingang hat. Eine hohe Impedanz "simuliert" einen offenen Stromkreis, wenn Sie ihn an einen Stromkreis mit niedrigerer Impedanz anschließen, damit das Vorhandensein des Girino den zu messenden Stromkreis nicht zu sehr beeinträchtigt.
  • Nach dem Widerstand befindet sich ein Emitterfolger, der das Signal entkoppelt und die folgende Elektronik schützt.
  • Es gibt einen einfachen Offset, der mit einem Spannungsteiler einen 2, 5-V-Pegel erzeugt. Es ist an einen Kondensator angeschlossen, um ihn zu stabilisieren.
  • Es gibt einen nicht invertierenden Summenverstärker, der das eingehende Signal und den Offset summiert. Ich habe diese Technik verwendet, weil ich auch negative Signale sehen wollte, da der Arduino ADC nur Signale zwischen 0 V und 5 V sehen konnte.
  • Nach dem Summenverstärker gibt es einen weiteren Emitterfolger.
  • Mit einem Jumper können wir entscheiden, ob wir das Signal mit einem Offset versorgen möchten oder nicht.
Der Operationsverstärker, den ich verwenden wollte, war ein LM324, der zwischen 0 V und 5 V, aber auch zwischen -12 V und 12 V arbeiten kann. Dies gibt uns mehr Möglichkeiten mit den Netzteilen. Ich habe auch einen TL084 ausprobiert, der viel schneller als der LM324 ist, aber eine doppelte Stromversorgung benötigt. Sie haben beide die gleiche Pinbelegung und können daher ohne Änderung der Schaltung geändert werden.

Schritt 19: Kondensatoren umgehen

Bypass-Kondensatoren sind Kondensatoren, mit denen die Stromversorgungen von integrierten Schaltkreisen (IC) gefiltert werden. Sie sollten so nah wie möglich an den Nahrungspins des IC angebracht werden. Sie werden normalerweise in Paaren, einer Keramik und einer Elektrolyse verwendet, da sie unterschiedliche Frequenzen herausfiltern können.

Schritt 20: Stromquellen

Ich habe für den TL084 ein doppeltes Netzteil verwendet, das für den LM324 in ein einzelnes Netzteil umgewandelt werden kann.

Auf dem Bild sehen wir, dass ich ein paar Spannungsregler verwendet habe, einen 7812 für +12 V und einen 7912 für -12 V. Die Kondensatoren werden wie üblich zur Stabilisierung der Pegel verwendet und ihre Werte sind die vorgeschlagenen in den Datenblättern.

Um ± 12 V zu haben, müssen natürlich mindestens 30 V am Eingang anliegen, da die Spannungsregler einen höheren Eingang benötigen, um einen stabilisierten Ausgang bereitzustellen. Da ich kein solches Netzteil hatte, habe ich den Trick angewendet, zwei 15-V-Netzteile in Reihe zu verwenden. Einer der beiden ist mit dem Arduino-Stromanschluss verbunden (so dass sowohl der Arduino als auch mein Stromkreis gespeist werden) und der andere direkt mit dem Stromkreis.

Es ist kein Fehler, die +15 V des zweiten Netzteils an den GND des ersten anzuschließen! So erhalten wir eine -15 V mit isolierten Netzteilen.

Wenn ich kein Arduino und zwei Netzteile mit mir herumtragen möchte, kann ich immer noch die vom Arduino bereitgestellten +5 V verwenden, um diese Jumper zu wechseln (und den LM324 zu verwenden).

Schritt 21: Vorbereiten eines Shield Connector

Ich habe mich immer über die Anschlüsse geärgert, die ich finden konnte, um ein Arduino-Schild herzustellen, weil sie immer zu kurze Stifte haben und die von mir verwendeten Platinen nur auf einer Seite verlötet werden können. Also habe ich mir einen kleinen Trick ausgedacht, um die Stifte länger zu machen, damit sie gelötet und in das Arduino eingesetzt werden können.

Wenn Sie den Stiftstreifen wie auf dem Bild in die Platine einführen, können Sie die Stifte drücken, um sie nur auf einer Seite des schwarzen Kunststoffs zu haben. Dann können wir sie auf derselben Seite löten, auf der sie in das Arduino eingefügt werden.

Schritt 22: Löten und Testen

Ich kann Ihnen nicht alle Lötvorgänge der Schaltung zeigen, da sie viele Versuche und Irrtümer durchgemacht hat. Am Ende wurde es ein wenig chaotisch, aber nicht schlecht, obwohl ich die Unterseite nicht zeigen werde, weil das wirklich chaotisch ist.

Zu diesem Zeitpunkt gibt es nicht viel zu sagen, da ich bereits alle Teile der Schaltung ausführlich erklärt habe. Ich habe es mit einem Oszilloskop getestet, das mir ein Freund geliehen hat, um die Signale an jedem Punkt der Schaltung zu sehen. Es scheint, dass alles in Ordnung funktioniert und ich bin ziemlich zufrieden.

Der Anschluss für das eingehende Signal könnte für jemanden, der nicht aus der Hochenergiephysik stammt, etwas seltsam erscheinen. Es handelt sich um einen LEMO-Anschluss. Es ist der Standardstecker für nukleare Signale, zumindest in Europa wie in den USA. Ich habe hauptsächlich BNC-Stecker gesehen.

Schritt 23: Testsignale

Um die Schaltung und die Datenerfassung (DAQ) zu testen, habe ich ein zweites Arduino mit einer einfachen Skizze verwendet, die Rechteckimpulse mit unterschiedlichen Längen erzeugt. Ich habe auch ein Python-Skript geschrieben, das mit Girino spricht und ihm sagt, dass er einige Datenreihen erfassen und eine davon in einer Datei speichern soll.
Sie sind beide an diesen Schritt gebunden.

Anhänge

  • TaraturaTempi.ino herunterladen
  • readgirino.py herunterladen

Schritt 24: Zeitkalibrierung

Mit den Testsignalen habe ich den horizontalen Maßstab der Diagramme kalibriert. Durch Messen der Breite der Impulse (die bekannt sind, weil sie erzeugt wurden) und Auftragen der gemessenen Impulsbreiten gegen die bekannten Werte erhalten wir eine hoffentlich lineare Darstellung. Wenn Sie dies für jede Prescaler-Einstellung tun, haben wir die Zeitkalibrierung für alle Erfassungsraten.

Auf den Bildern sehen wir alle Daten, die ich analysiert habe. Das Diagramm "Angepasste Steigungen" ist am interessantesten, da es die tatsächliche Erfassungsrate meines Systems bei jeder Prescaler-Einstellung angibt. Die Steigungen wurden als [ch / ms] -Zahl gemessen, dies entspricht jedoch [kHz], sodass die Steigungswerte tatsächlich kHz oder auch kS / s (Kiloproben pro Sekunde) sind. Das bedeutet, dass wir mit dem auf 8 eingestellten Vorteiler eine Akquisitionsrate von:

(154 ± 2) kS / s

Nicht schlecht, oder?

Während wir aus dem Diagramm "Angepasste y-Abschnitte" einen Einblick in die Systemlinearität erhalten. Alle y-Abschnitte sollten Null sein, da bei einem Signal mit einer Länge von Null ein Impuls mit einer Länge von Null entsprechen sollte. Wie wir in der Grafik sehen können, sind alle mit Null kompatibel, nicht jedoch mit dem 18-Prescaler-Datensatz. Dieser Datensatz ist jedoch der schlechteste, da er nur zwei Daten enthält und seiner Kalibrierung nicht vertraut werden kann.

Im Folgenden finden Sie eine Tabelle mit den Erfassungsraten für jede Prescaler-Einstellung.

PrescalerErwerbsquote [kS / s]
1289, 74 ± 0, 04
6419, 39 ± 0, 06
3237, 3 ± 0, 6
1675, 5 ± 0, 3
8153 ± 2
Die genannten Fehler stammen von der Gnuplot Fit Engine und ich bin mir nicht sicher.

Ich habe auch versucht, die Raten ungewichtet anzupassen, da man sieht, dass sie sich ungefähr verdoppeln, wenn sich die Vorskalierung halbiert. Dies sieht aus wie ein inverses Proportionalitätsgesetz. Also habe ich die Raten gegen die Prescaler-Einstellungen mit einem einfachen Gesetz von angepasst

y = a / x

Ich habe einen Wert für ein von

a = 1223

Mit einem χ² = 3, 14 und 4 Freiheitsgraden bedeutet dies, dass das Gesetz mit einem Konfidenzniveau von 95% akzeptiert wird!

Schritt 25: Fertig! (Fast)

Am Ende dieser langen Erfahrung bin ich sehr zufrieden, weil
  • Ich habe viel über Mikrocontroller im Allgemeinen gelernt.
  • Ich habe viel mehr über den Arduino ATMega328P gelernt.
  • Ich hatte einige praktische Erfahrungen mit der Datenerfassung, nicht indem ich etwas verwendet habe, was bereits getan wurde, sondern indem ich etwas gemacht habe.
  • Ich erkannte ein Amateuroszilloskop, das gar nicht so schlecht ist.
Ich hoffe, dass dieser Leitfaden für jeden nützlich ist, der ihn liest. Ich wollte es so detailliert schreiben, weil ich das alles auf die harte Tour gelernt habe (im Internet surfen, das Datenblatt lesen und durch viel Versuch und Irrtum) und ich möchte jeden von dieser Erfahrung verschonen.

Schritt 26: Fortsetzung folgt ...

Das Projekt ist jedoch noch lange nicht abgeschlossen. Was fehlt, ist:
  1. Ein Test mit verschiedenen analogen Signalen (mir fehlt ein analoger Signalgenerator);
  2. Eine grafische Benutzeroberfläche für die Computerseite.
Während für Punkt 1. Ich bin nicht sicher, wann es abgeschlossen sein wird, weil ich nicht vorhabe, in naher Zukunft einen zu kaufen / bauen.

Für Punkt 2 könnte die Situation besser sein. Ist jemand bereit, mir dabei zu helfen? Ich habe hier ein schönes Python-Oszilloskop gefunden:
//www.phy.uct.ac.za/courses/python/examples/moreexamples.html#oscilloscope-and-spectrum-analyser
Ich würde es gerne an Girino anpassen, aber ich akzeptiere Vorschläge.

Ähnlicher Artikel