Bei Adafruit kann man eine Arduino-Library für den 4-Kanal-ADC ADS1015 bzw. ADS1115 beziehen. Man findet dort auch ein ausführliches Tutorial A guide to the ADS1115 and ADS1015 analog converters. Ein Breakout-Board kann dort ebenfalls bezogen werden.
Leider kann man mit der Bibliothek den kontinuierlichen Modus nicht ansprechen, bei dem der ADC selbständig mit einer einstellbaren Rate durchgehend Messungen vornimmt. Die Klasse ist auch schlecht erweiterbar, da viele der intern benutzen Methoden statisch in der .cpp-Datei deklariert und somit nicht öffentlich zugänglich sind. Zur Sicherstellung, dass die Messung abgeschlossen ist, enthalten die Leseroutinen einen Aufruf von delay(). Da geht gar nicht! Also selber machen!
Für ESP8266:
Die Standard-Bibliothek
TwoWire
kann ebenfalls nicht zum Datenaustausch während eines Interrupts benutzt werden. Diese Bibliothek
legt ihre Methoden im Flash ab. Dies führt beim Aufruf innerhalb einer ISR zu Problemen (s. auch
ESP8266 FAQ: IRAM & ISR). Die an
TwoWire angelehnte Bibliothek
UrsTwi vermeidet dieses Problem.
Inhaltsverzeichnis
Version | Anpassungen |
---|---|
1.0 (2018-02-10) | Basis-Version |
1.1 (2018-03-01 | umgestellt auf UrsTwi readRegister() mit Attribut ICACHE_RAM_ATTR versehen Fehler bei Ads1x15Gain++ und Ads1x15Gain-- behoben |
1.2 (2018-03-02) | getGainString() hinzugefügt |
1.3 (2020-04-12) | - Methode reset() hinzugefügt - Ads1x14Address umbenannt in Ads1x15Address - ADS1015_REG_... umbenannt in ADS1X15_REG_... - Für Arduino (Uno) erweitert |
1.4 (2020-04-13) | Werte für Ads1115Sps waren vollkommen falsch. Korrigiert. |
1.5 (2020-04-14) | readRegister() hat den übergebenen Registerparameter
ignoriert und immer nur das Conversion-Register ausgelesen. |
1.6 (2020-04-14) | setMaxSps() hinzugefügt. |
2.0 (2020-04-23) | - Auch die Arduino-Variante muss mit einer TWI-Instanz konstruiert werden. - Methode getType hinzugefügt - Die User-ISR beim Continuous-Mode umgestellt. Sie erhält jetzt den aktuell ermittelten Rohwert und einen Zeiger auf die zugehörige UrsAds1x15-Instanz. - Interrupts sind beim Eintritt in die User-ISR beim Continuous-Mode wieder abgeschaltet (nointerrupts()). |
2.1 (2020-12-21) | Funktioniert nun auch mit dem ESP32 |
2.2 (2021-11-06) | I²C-Adressen korrigiert. |
Hinweise zum Betrieb des Chips und Datenblätter sind bei und sonst boards ausgeführt.
Die wesentlichen Komponenten der Bibliothek sind die Klassen UrsAds1x15 für die Elemente, die bei dem ADS1015 und ADS1115 identischen sind. Diese Klasse besitzt einen geschützten Konstruktor und kann nicht instanziiert werden. Die Klassen UrsAds1015 und UrsAds1115 ergänzen UrsAds1x15 um die spezifischen Elemente und können verwendet werden.
Hinzu kommen eine Reihe von Konstanten zur Konfiguration des Chips, die allesamt, um Verwechselungen auszuschließen, in Enumerationsklassen gekapselt wurden. Dies sind:
Den Konstruktoren von UrsAds1015 und UrsAds1115 kann eine der möglichen I²C-Adressen und das zu benutzende I²C-Interface übergeben werden. Für beide Parameter sind Voreinstellungen hinterlegt. Bei den meisten erhältlichen Breakout-Boards ist die I²C-Adresse auf 0x48 (=Ads1x14Address::Default) eingestellt. Das Arduino-System stellt das Standard-I²C-Interface über das Objekt Wire bereit.
Das I²C-Interface muss vor der ersten Verwendung durch UrsAds1x15 initialisiert werden. Das I²C-Interface des Arduino kann mit Bitraten bis zu 400kHz (fast mode) konfiguriert werden. Der Standard ist 100kHz ('standard mode'). Der ADS1x15 kann mit den 'fast mode' betrieben werden. Wenn alle Geräte, die an dem I²C-Bus hängen, mit dieser Frequenz klarkommen, sollte man das Interface im 'fast mode' betreiben.
ESP8266 | Arduino | |
|
|
Mit readADCmV() wird die Konvertierung gestartet, der Messwert ausgelesen und in mV umgerechnet.
Serial.print("Messwert an AIN0: ");
Serial.print(ads.readADCmV(Ads1x15Mux::Single_0));
Serial.println(" mV");
Beim kontinuierlichen Betrieb beginnt der ADS1x15 mit einer neuen Konvertierung sobald die laufende abgeschlossen ist. Beim Umschalten in den kontinuierlichen Modus wird der ALERT/RDY-Pin so konfiguriert, dass immer nach Beendigung einer Konvertierung dieser Pin ein kurzer (ca. 8µs) Puls mit Low-Potential erzeugt wird. Wird der Pin an einen Eingang des Arduino gelegt, kann dies einen Interrupt auslösen, der zum Einlesen des jeweils letzten Messwerts genutzt werden kann.
Mit startContinuousMode() wird der kontinuierliche Messmodus gestartet. Der Methode werden vier Argumente übergeben:
startContinuousMode() macht folgendes:
Der Auslesen der Messwerte erfolgt dann mit readContinuousModeRaw(). Der kontinuierliche Modus kann mit stopContinuousMode() beendet werden.
Diese Methode sollte so kurz wie möglich gehalten werden! Da in dieser Routine i.d.R. ein Auslesen des ADS1x15 erfolgt sollte die Taktfrequenz des I²C-Interface so hoch wie möglich gewählt werden. Es müssen etwa 26 Bit transferiert werden (Start, 8 x I²C-Adresse, 16 x Datenbits, Stop). Bei einer Taktfrequenz sind 400 kHz werden hierfür rd. 65 µs benötigt. Hinzu kommt noch einiger Code, so dass mit gut 70 µS zu rechnen ist. Bei der Standard-Sample-Rate von 1600 Messungen pro Sekunde erfolgt der Interrupt etwa alle 625 µs. Die ISR benötigt somit gut 11% der Prozessorzeit.
Bei einer I²C-Taktfrequenz von nur 100 kHz betragt die Laufzeit der ISR etwa 300 µs, also entsprechend 48% der Rechenzeit.
Aus diesem Zeit-Grund wird auch keine Methode zur Umrechnung in mV zur Verfügung gestellt. Floating-Point-Operationen sollten außerhalb der ISR erfolgen.
TwoWire, die Arduino I²C-Klasse, ist nicht threadsicher! Solange der kontinuierliche Modus aktiv ist, muss der I²C-Bus für die ISR reserviert bleiben. readAdcRaw() und readADCmV() sind in diesem Modus wirkungslos, weil UrsAds1x15 den Zugriff auf den Bus verweigert. Sie liefern den Wert 0.
Sind weitere Geräte am Bus angeschlossen, muss programmtechnisch darauf geachtet werden, dass keine konkurrierenden Zugriffe erfolgen.
Das Beispiel zeigt den Betrieb im kontinuierlichen Modus mit einem Memos D1 mini (ESP8266).Das Bild zeigt den Schaltplan. ALERT/RDY des ADS1115 ist an D4 des Wemos D1 mini angeschlossen. Die zu messende Spannung liegt an A0 des ADS1115. Ein Widerstand von etwa 10 kΩ sorgt für ein sicheres Potential am ALERT/RDY-Pin.
Das Programm soll folgende Aufgabe erledigen. Es sollen 32 Messungen mit einer Sample-Rate von 1600 durchgeführt werden. Die entspricht einer Periode bei einem 50 Hz Wechselstrom. Die Werte sollen als mV-Angabe über die serielle Schnittstelle ausgegeben werden.
#include <UrsAds1x15.h>
#include <UrsTwi.h> // entfällt bei Arduino
UrsTwi twi; // entfällt bei Arduino
UrsAds1015 ads(twi); // 12-Bit ADC // Arduino: UrsAds1015 ads;
//UrsAds1115 ads(twi); // 16-Bit ADC
const uint8_t maxData = 32; // Anzahl Messwerte
volatile int16_t data[maxData]; // Array zur Aufnahme der Daten
volatile uint8_t idex = 0; // Index für data
const uint8_t alertPin = D4; // Interrupt-Pin
// ISR
void ICACHE_RAM_ATTR ADCISR() { // Arduino: void ADCISR() {
if (idex < maxData)
data[idex++] = ads.readContinuousModeRaw();
}
void setup(void) {
Serial.begin(115200);
Serial.println(F("\n\n----------------------------\nADS1015\n"));
twi.setClock(400000L); // I²C-Taktfrequenz: 400 kHz
twi.begin(); // I²C-Interface starten
ads.setSps(Ads1015Sps::Sps1600); // 1600 Messungen pro Sekude
ads.setGain(Ads1x15Gain::Gain_16); // Höchste Empfindlichkeit
ads.startContinuousMode(Ads1x15Mux::Diff_0_1, ADCISR, alertPin); // Messvorgang starten
}
void loop(void) {
if (idex < maxData)
return; // Messung läuft noch
ads.stopContinuousMode(); // Messung stoppen
Serial.println(F("\n\n----------------------------\n"));
for (int i = 0; i < maxData; i++) { // Messwerte ausgeben
Serial.printf("%3i ", i); Serial.print(ads.getMilliVoltsPerBit()*data[i]); Serial.println(" mV");
}
delay(5000); // 5 Sekunden Pause
// Messung neu starten
idex = 0;
ads.startContinuousMode(Ads1x15Mux::Diff_0_1, ADCISR, alertPin);
}
Das Ergebnis dieser Aktion als Excel-Diagramm:
Methode | Funktion | Anmerkung | ||||
---|---|---|---|---|---|---|
UrsAds1015(UrsTwi& twi,
Ads1x14Address i2cAddress = Ads1x14Address::Default) UrsAds1115(UrsTwi& twi, Ads1x14Address i2cAddress = Ads1x14Address::Default) |
Initialisiert eine neue Instanz der Klasse.
|
Das IC-Interface muss außerhalb der Klasse initialisiert werden. | ||||
void setGain(const Ads1x15Gain gain) | Legt den Verstärkungsfaktor fest. | Auswirkung erst bei der nächsten Messung. Kein Einfluss auf aktuellen kontinuierlichen Betrieb. | ||||
Ads1x15Gain getGain() | Ruft den aktuell eingestellten Verstärkungsfaktor ab. | |||||
String getGainString() | Liefert den aktuell eingestellten Verstärkungsfaktor als String. | Z.B. "2/3", "8", "26" | ||||
float getMilliVoltsPerBit() | Ruft den aktuellen Umrechnungsfaktor Bit -> mV ab. | |||||
uint8_t getConversionDelay() | Ruft die Zeitspanne in ms ab, auf die für eine Konvertierung gewartet wird. | |||||
void setSps(Ads1015Sps Sps) void setSps(Ads1115Sps Sps) |
Legt die Messrate fest. | Auswirkung erst bei der nächsten Messung. Kein Einfluss auf aktuellen kontinuierlichen Betrieb. | ||||
Ads1015Sps getSps() Ads1115Sps getSps() |
Ruft die aktuell eingestellte Messrate (Code) ab oder legt sie fest. | Bei einem Standard-Arduino (ATmega328p, 16MHz) muss zum
Betrieb beim ADS1015 im Continous-Mode bei 3300 SPS die TWI-Taktfrequenz auf min. 400kHz (Fast-Mode)
eingestellt werden. Bei der Standardeinstellung 100kHz dauert das Einlesen des Wadlungsresultats per readContinuousModeRaw() gut 320μs + die zusätzlich auszuführenden Anweisungen. Es stehen aber nur max. 300μs zur Verfügung. Der nächste Interrupt erfolgt dann bevor die ISR des des vorhergehenden beendet wurde. |
||||
uint16_t getSamplesPerSeconds() | Ruft die Messrate (Messungen pro Sekunde) ab. | |||||
int16_t readAdcRaw(const Ads1x15Mux mux) | Führt eine Einzelmessung für den angegebenen Kanal durch, liefert den Rohwert. | Keine Funktion im kontinuierlichen Modus. Liefert 0. Beim ADS1015 mit 12 Bit Auflösung sind die niedrigsten vier Bit immer 0. |
||||
float readADCmV(const Ads1x15Mux mux) | Führt eine Einzelmessung für den angegebenen Kanal durch, liefert mV. | Keine Funktion im kontinuierlichen Modus. Liefert 0. | ||||
void startContinuousMode( const Ads1x15Mux mux, void(*ISR)(void), const uint8_t alertPin, const bool usePullUpForAlertPin = true); |
Startet den kontinuierlichen Modus.
|
|||||
void stopContinuousMode() | ||||||
int16_t readContinuousModeRaw() | Liefert den aktuellen Rohwert (Conversion Register). | |||||
bool isContinuousMode() | Liefert true, wenn der kontinuierliche Modus aktiv ist. | |||||
void reset() | Versetzt den Chip in den Power-Down-Zustand. | |||||
void setMaxSps() | Stellt die maximale Umwandlungsrate ein. |
|
||||
Ads1x15Type getType() | Liefert den ADS1x15-Typ. | Ads1x15Type::ADS1015 oder Ads1x15Type::ADS1115 |
int16_t UrsAds1x15::readAdcRaw(Ads1x15Mux channel) {
if (_isContinuousMode)
return 0;
// Config Register zusammenstellen
uint16_t config = ADS1015_REG_CONFIG_CQUE_NONE | // Disable the comparator
ADS1015_REG_CONFIG_CLAT_NONLAT | // Non-latching
ADS1015_REG_CONFIG_CPOL_ACTVLOW | // Alert/Rdy active low
ADS1015_REG_CONFIG_CMODE_TRAD | // Traditional comparator
ADS1015_REG_CONFIG_MODE_SINGLE | // Single-shot mode
ADS1015_REG_CONFIG_OS_SINGLE; // Set 'start single-conversion' bit
config |= (uint16_t)_SpsCode; // Messrate/-dauer auswählen
config |= (uint16_t)_gain; // Vorverstärker auswählen
config |= (uint16_t)channel; // Kanal auswählen
// Config Register ausgeben
writeRegister(ADS1015_REG_POINTER_CONFIG, config);
// Auf das Ende der Messung warten
delay(_conversionDelay);
// Messwert auslesen
return (int16_t)readRegister(ADS1015_REG_POINTER_CONVERT);
}
float readADCmV(const Ads1x15Mux mux) { return readAdcRaw(mux) * getMilliVoltsPerBit();}
Der Rohdatenwert wird mit der aktuellen Einstellung des Verstärkers multipliziert.
void UrsAds1x15::startContinuousMode(const Ads1x15Mux mux, void(*ISR)(void), const uint8_t alertPin,
const bool usePullUpForAlertPin) {
uint16_t config = ADS1015_REG_CONFIG_CQUE_1CONV | // Disable the comparator
ADS1015_REG_CONFIG_CLAT_NONLAT | // Non-latching
ADS1015_REG_CONFIG_CPOL_ACTVLOW | // Alert/Rdy active low
ADS1015_REG_CONFIG_CMODE_TRAD | // Traditional comparator
ADS1015_REG_CONFIG_MODE_CONTIN; // Continuous conversion mode
config |= (uint16_t)_SpsCode; // Messrate/-dauer auswählen
config |= (uint16_t)_gain; // Vorverstärker auswählen
config |= (uint16_t)mux; // Kanal auswählen
writeRegister( ADS1015_REG_POINTER_HITHRESH, 0x8000); // High- & Low-Threshold-Register für RDY konfigurieren
writeRegister( ADS1015_REG_POINTER_LOWTHRESH, 0);
// Interrupt initialisieren
if (usePullUpForAlertPin)
pinMode(alertPin, INPUT_PULLUP); // PullUp auf dem Interrupt-Pin
else
pinMode(alertPin, INPUT);
attachInterrupt(alertPin, ISR, FALLING); // ISR registrieren
// Wandlung starten
writeRegister(ADS1015_REG_POINTER_CONFIG, config);
// Auslesen des Konvertierungsregisters vorbereiten
_twi.beginTransmission(_i2cAddress);
_twi.write(ADS1015_REG_POINTER_CONVERT);
_twi.endTransmission();
_isContinuousMode = true;
}
Anzumerken ist eigentlich nur der Schluss der Methode. Das Pointer-Register des ADS1x15 wird auf ADS1015_REG_POINTER_CONVERT gesetzt. Dass bewirkt, dass folgende Leseaufrufe das Konvertierungsregister abfragen. Das ist dann mit drei übertragenen Bytes getan und reduziert die Verweildauer in der ISR bei Aufruf von readContinuousModeRaw() erheblich. Über das Setzen von _isContinuousMode wird verhindert, dass andere Routinen dieser Klasse den Inhalt des ds Pointer-Registers überschreiben.
int16_t ICACHE_RAM_ATTR UrsAds1x15::readContinuousModeRaw() const {
if (!_isContinuousMode)// kontinuierlicher Modus aktiv?
return 0;
// Konvertierungsregister abrufen
// Pointer-Register wurde bereits in startContinuousMode gesetzt.
_twi.requestFrom(_i2cAddress, (uint8_t)2);
uint16_t res = ((_twi.read() << 8) | _twi.read());
return (int16_t)res;
}
Der Aufruf dieser Methode macht nur im kontinuierlichen Modus Sinn. Es wird davon ausgegangen, dass das Pointer-Register auf das Konvertierungsregister gesetzt ist.
void UrsAds1x15::stopContinuousMode() {
if (!_isContinuousMode)
return;
detachInterrupt(_alertPin);
// Config Register zusammenstellen
uint16_t config = ADS1015_REG_CONFIG_CQUE_NONE | // Disable the comparator
ADS1015_REG_CONFIG_CLAT_NONLAT | // Non-latching
ADS1015_REG_CONFIG_CPOL_ACTVLOW | // Alert/Rdy active low
ADS1015_REG_CONFIG_CMODE_TRAD | // Traditional comparator
ADS1015_REG_CONFIG_MODE_SINGLE;
writeRegister(ADS1015_REG_POINTER_CONFIG, config);
writeRegister(ADS1015_REG_POINTER_HITHRESH, 0x7FFF); // Standardwerte lt. Datenblatt
writeRegister(ADS1015_REG_POINTER_LOWTHRESH, 0x8000);
_isContinuousMode = false;
}
Der Modus wird wieder auf 'Single-Shot-Shutdown' eingestellt. Die High- und Low-Threshold-Register müssen wieder ihre Standardwerte erhalten.
Das ZIP-Archiv für Bibliothek UrsAds1x15 zum Download. Die entpackten Dateien ins Verzeichnis <user>\Documents\Arduino\libraries kopieren (siehe Installing Additional Arduino Libraries).
Das Archiv enthält die Bibliotheksdateien