Motivation

Für ein Projekt sollen Messungen mit dem AD7606 (ADC mit acht Kanälen und max. 200kSpS) mit möglichst hoher Taktfrequenz durchgeführt werden. Die folgende Klasse für den STM32F103 (Bluepill) schafft in gut 5μs einen Messzyklus. Timergesteuert, wegen der Zykluszeitkonstanz, kann eine Zykluszeit von 6μs, knapp 167MHz, erreicht werden.

Klasse UrsAD7606bp

Voraussetzung

Verwendung

Methodenübersicht

Beispiel (STM32F1-AD7606-bytepar)

Übersicht

Klasse SerialControl

Klasse AD7606Control

Hauptprogramm

Download


Versionshistorie

Version Anpassungen
1.0 (2021-11-05) Basis-Version
1.1 (2021-11-09) Datenübernahme war falsch. Übernahme muss nach steigender Flanke des Clock-Signals geschehen.
2021-11-14 Die Anschlusstabelle enthielt Fehler. Korrekt ist CVA ⇒ PB6, DB14 (HBEN) ⇒ LOW (GND).
Das Beispielprogramm hatte einen Fehler in der Ausgabe-Funktion (printReport).
1.1 (2021-11-26)
  • Funktion reset kann eine Blindmessung durchführen.
  • Datentyp bool bei Methode begin ersetzt durch Enumeration AD7606DummyRead.
  • Methode umbenannt: readAndSample -> sampleAndRead.

Hinweis: Zur Installation des Arduino-Bootloaders auf einem STM32 siehe Arduino-FAQ.

Zur Beschreibung des AD7606 und des Entwicklungsboards siehe URS AD7606.

Wichtig: Damit die Geschwindigkeit erreicht wird, muss mit der Kompiler-Optimierung Fastest (-O3) kompiliert werden!

Klasse UrsAD7606bp

Die Klasse dient zur Kontrolle eines AD7606. Die Messdaten werden byte-seriell eingelesen. Bei einem STM32F103 (Bluepill) ist es möglich, Messungen mit einer minimalen Taktdauer von knapp 6 μs (≈ max. 167kSpS) durchzuführen.

Voraussetzung

Das AD7606-Board ist auf serielle Übertagung eingestellt (Lötbrücke auf Stellung SPI). Eingelesen werden die Daten über Port A (PA0..PA7). Die Steuersignale sind sämtlich an Port B angeschlossen.

Die folgenden Signale des AD7606 werden von der Klasse kontrolliert: RST (Reset), CVA (CONVST A , Konvertierung Start A, gemeinsam mit CVB), BUSY (Messung läuft), CS (Chip select), RD (Read, Taktsignal).

Alle anderen Signale, insbesondere die zur Konfiguration (z.B. Range) sind fest verdrahtet. Im Beispiel (s.u.) ist die Verschaltung wie in der nachfolgenden Tabelle. Es ist kein Problem weitere Signalsteuerungen einzubauen, um z.B. den Messbereich flexibel anzusteuern.

AD7606 Bluepill AD7606 Bluepill
GND GND +5V 5V
OS1 GND (LOW) OS0 GND (LOW)
RAGE GND (LOW) OS2 GND (LOW)
CVB PB6 CVA PB6
RD PB10 (SCK) RST PB11
BUSY PB0 CS PB1 (SS)
FRST - VIO 3.3V
DB1 PA1 DB0 PA0
... - ... -
DB7 PA7 DB6 PA6
DB9 - DB8 -
...   ...  
DB15/BYTE SEL 3.3V (HIGH) DB14/HBEN GND (LOW)
Lötbrücke Stellung SPI

Verwendung

Zur Performancesteigerung ist die Klasse ist  als C++-Template-Klasse angelegt. Die Steuerleitungen werden über die Typ-Parameter deklariert. Somit liegen sie in der Klasse als Konstante vor und müssen nicht aus dem RAM geladen werden.

Außerdem sind sämtliche Methoden als static inline deklariert. Dies erspart den Overhead des Aufrufs einer Member-Funktion.

Sämtliche Steuerleitungen sind an den Port B angeschlossen und werden durch direkten Zugriff auf die GPIO-Register kontrolliert. Die im STM32duino verwendete Maple-Bibliothek (libmaple) stellt hierfür die Strukturen gpioa und gpiob zur Verfügung. Sollen die Ports geändert werden, muss dies entsprechend geändert werden.

Methodenübersicht

Hilfsklassen (UrsSTM32F1-AD7606.h)

Die Enumeration AD7606Range ist so angelegt, dass die Member den Umrechnungsfaktoren LBS -> Volt entsprechen:

// Werte für die Auflösung. 5 Volt oder 10 Volt
enum class AD7606Range {
   Volt5 = 152580,
   Volt10 = 305175
};

AD7606DataSet nimmt die Rohwerte einer Messung mit acht Kanälen auf.

// Zur Aufnahme einer Messung mit 8 Kanälen
struct AD7606DataSet {
   int16_t channel [8];
};

Die Enumeration AD7606DummyRead liefert Konstanten für den Parameter dummyRead. Die Verwendung des Datentyps bool und dann die Wertangabe true oder false ist nicht sprechend.

// Zur Festlegung, ob zu Beginn eine Blindmessung durchgeführt werden soll.
enum class AD7606DummyRead : bool {
   disable = false,
   enable = true
};

Definition der Klasseninstanz

Bei der Definition der Klasseninstanz müssen die Steuersignale und die eingestellt Auflösung als Typparameter übergeben werden:

template<uint8_t pinRST, uint8_t pinCVA, uint8_t pinBUSY, uint8_t pinRD, uint8_t pinCS, AD7606Range range> class UrsAD7606bs 

Also z.B.:

constexpr uint8_t rstPin = PB11;   // (RST) Pin für den Reset
constexpr uint8_t cvaPin = PB6;    // (CVA) Pin startet die Messung (mit CVB verbunden)
constexpr uint8_t busyPin = PB0;   // (BUSY) Pin für BUSY-Signal
constexpr uint8_t clockPin = PB10; // (RD) Pin für die Ausgabe des Taktsignals
constexpr uint8_t ssPin = PB1;     // (CS) Pin für die Ausgabe des Chip-Select-Signals (Slave-Select)

UrsAD7606bs<rstPin, cvaPin, busyPin, clockPin, ssPin, AD7606Range::Volt5> ursAD7606bs;

Methoden

Methode Funktion Anmerkung
void begin(AD7606DummyRead dummyRead = AD7606DummyRead::enable) Initialisiert die Schnittstelle.
Der Pin-Modus und der Ruhezustand der Pins wird eingestellt. Es wird ein Reset-Signal für den AD7606 ausgelöst.
dummyRead: bei enable wird eine Blindmessung durchgeführt.
Die Pins RST, CVA, RD und CS werden auf den OUTPUT-Modus eingestellt, BUSY auf INPUT. Der Ruhezustand für RD und CS ist HIGH, für RST und CVA ist er LOW.

Die erste Konvertierung nach einem Reset hat gelegentlich unsinnige Werte ergeben. Mit dummyRead kann eingestellt werden, dass eine Blindmessung ausgeführt wird.
void reset(
  AD7606DummyRead dummyRead= AD7606DummyRead::enable)
Sendet ein Reset-Signal an den AD7606.
dummyRead: bei enable  wird eine Blindmessung durchgeführt.
Mit dummyRead kann eingestellt werden, dass eine Blindmessung ausgeführt wird.
void start() Startet eine Messung. Der CVA-Pin erhält einen HIGH-Puls.
bool isBusy() Gibt an, ob eine lfd. Messung noch nicht abgeschlossen ist. true, solange die Messung andauert.
void readData(AD7606DataSet* dest) Liest die Daten des letzten Messvorgangs aus. Die Daten werden nach dest übertragen.  
void sample() Startet eine Messung und wartet, bis die Messung abgeschlossen ist. Kehrt zurück, sobald isBusy false liefert.
sampleAndRead(AD7606DataSet* dest) Startet eine Messung, liest währenddessen die Werte der vorhergehenden Messung aus und wartet, bis die Messung abgeschlossen ist.
Die Daten werden nach dest übertragen.
Diese Methode erlaubt es, Messungen mit der maximalen Frequenz durchzuführen. Die Dauer liegt bei etwa 4μs, ist nicht sehr verlässlich.
void startAndRead(AD7606DataSet* dest) Startet eine Messung, liest währenddessen die Werte der vorhergehenden Messung aus. Kehrt zurück, sobald die Daten eingelesen wurden.
Die Daten werden nach dest übertragen.
Bei der Rückkehr ist die lfd. Messung noch nicht abgeschlossen. Deshalb beim Aufruf zunächst gewartet bis eine evtl. laufende Messung abgeschlossen ist.
float value(int16_t rawValue) Rechnet einen eingelesenen Rohwert in einen Spannungswert um. Der Umrechnungsfaktor ist ein Typparameter der Klasse.

Beispiel (STM32F1-AD7606-bytepar)

Im Beispiel wird eine einfache Ansteuerung des AD7606 über die serielle Schnittstelle gezeigt. Nach einem Startkommando wird eine einstellbare Anzahl von Messungen in einer einstellbaren Zykluszeit durchgeführt und zwischengespeichert. Ist die eingestellte Anzahl an Messungen erreicht, wird der Messvorgang gestoppt und die ermittelten Werte ausgegeben.

Das Programm versteht drei Kommandos ("\n" = Zeilenvorschub, LF, ASCII 0x0A):

Das Einlesen der Kommandos geschieht mit der Methode Serial.readStringUntil(). Diese Methode hat ein relativ kurzes Timeout, so dass die einzelnen Zeichen des Kommandos schnell hintereinander versendet werden müssen. Ggf. kann das Timeout über Serial.setTimeout(milliSeconds) angepasst werden.

Zum Anschluss des AD7606 an den Bluepill: siehe oben, Abschnitt Voraussetzung.

Das ist die Ausgabe des Programms. Die Kanäle 2 und 8 sind an 3,3V des Bluepill-Boards angeschlossen, die anderen sind kurzgeschlossen.

Übersicht

Um das Beispiel übersichtlicher zu gestalten, wurden die Funktionalitäten in zwei Klassen ausgelagert:

Die Klasse SerialControl kapselt die serielle Schnittstelle Serial. Sie wertet den eigehenden Datenstrom bzgl. der Kommandos aus und ruft bei erkannten Kommandos eine entsprechende Callback-Funktion auf.

Die Klasse AD7606Control kontrolliert den Messvorgang. Sie stellt die Callback-Funktionen zur Kommandoausführung bereit. Zu Ansteuerung des AD7606 wird die Bibliotheksklasse UrsAD7606bp (s.o.) genutzt.

In dem Hauptprogramm STM32F1-AD7606-bytepar.ino werden dann nur diese beiden Klassen in setup initialisiert und in loop angesteuert.

Klasse SerialControl

Die Klasse wird mit den Zeigern auf die Callback-Funktionen initialisiert.

// Initialisiert die Klasseninstanz
//    timerControl: Callback für Kommando 't'
//    countControl: Callback für Kommando 'c'
//    doSample:     Callback für Kommando 's'
SerialControl(Callback doSample, UintCallback timerControl, UintCallback countControl)

Die Methode begin wird in setup aufgerufen und initialisiert die serielle Schnittstelle. handle, immer wieder in loop aufgerufen, analysiert den Input-Stream und ruft bei erkannten Kommandos die zugehörigen Callback-Funktionen auf.

Klasse AD7606Control

Die Messreihenaufnahme wird durch einen Timer-Interrupt gesteuert. Die zugehörige ISR muss als statische Funktion angelegt werden. Um das Beispiel einfach zu halten, wurden auch die anderen Member der Klasse als statische Member angelegt.

Die Methode begin wird in setup aufgerufen und initialisiert die Schnittstelle. Die Methoden setTime(uint microSeconds), setCount(uint sampleCount) und startSampling() sind die Methoden zur Kommando-Ausführung (Callback in SerialControl). Über hasFinished() kann abgerufen werden, ob eine Messreihe beendet wurde, also gültige Daten vorliegen. Das zugehörige interne Flag wird beim Aufruf der Funktion zurück gesetzt, so dass folgende Aufrufe den Wert false ergeben. printReport() gibt die Messreihe aus.

Zum Timing wird Timer1 (HardwareTimer(1)) benutzt. Die komplette Messreihenaufnahme erfolgt innerhalb der Timer-ISR.

// Messreihe per Timer-Interrupt aufnehmen.
void timerInterruptHandler() {
   if (currentSampleCount >= sampleCount - 1) { // Alle Messungen wurden gestartet
      sampleTimer.c_dev()->regs.gen->CR1 &= ~TIMER_CR1_CEN;  //Timer abschalten [sampleTimer.pause();]

      while (ursAD7606bs.isBusy()); // Warten, bis letze Messung beendet
      ursAD7606bs.readData(&samples[currentSampleCount]); // letzten Wert auslesen
      samplingDone = true; // Messreihe komplett
      return;
   }

   // Weitere Messungen notwendig
   ursAD7606bs.startAndRead(&samples[currentSampleCount++]);
}

Hauptprogramm

Wegen der Auslagerung der einzelnen Aufgaben in separate Klassen gestaltet sich das Hauptprogramm recht einfach:

AD7606Control ad7606; // Ermöglicht "ad7606." anstatt "AD7606Interface::"

// Serielle Schnittstelle definieren
// Alle Aktionen mit der seriellen Schnittstelle laufen über dieses Objekt
SerialControl serialControl(ad7606.startSampling, ad7606.setTime, ad7606.setCount);


void setup() {
   serialControl.begin();
   delay(500); // Der Treiber der USB-USART muss sich nach dem Programmieren wieder bei Windows anmelden
   serialControl.println("\n---------------------------------");
   serialControl.print(APP_NAME); serialControl.print(" Verion "); serialControl.println(VERSIONSTRING);

   ad7606.begin(&serialControl); // Schnittstelle initialisieren

   serialControl.println("loop running");
}

void loop() {
   if (ad7606.hasFinished()) { // Messreihe komplett
      ad7606.printReport();
   }

   serialControl.handle();
}

Download

Das ZIP-Archiv für Bibliothek UrsSTMF1-AD7606 zum Download. Die entpackten Dateien ins Verzeichnis <user>\Documents\Arduino\libraries kopieren (siehe Installing Additional Arduino Libraries).

Das Archiv enthält die Bibliotheksdateien