Motivation

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.

In­halts­ver­zeich­nis

Bibliothek UrsAds1x15

Verwendung

Beispiel

Methodenübersicht

Implementierung

Download


Versionshistorie

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.

Bibliothek UrsAds10x15

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:

Verwendung

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
UrsTwi twi(SDA, SCL);
UrsAds1015 ads(twi); // 12-Bit 

// ...
setup(){
// ...
  twi.setClock(400000L);
  twi.begin();
// ...
}
 
UrsAds1015 ads(Wire); // 12-Bit 

// ...
setup(){
// ...
  twi.setClock(400000L);
  twi.begin();
// ...
}

Einzelmessung

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");

Kontinuierlicher Betrieb

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.

Zur Interrupt-Service-Routine

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.

Beispiel (kontinuierlicher Modus)

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.

ADS1115 und Wemos D1 mini

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:

ADS1115 und Wemos D1 mini

Methodenübersicht

Methode Funktion Anmerkung
UrsAds1015(UrsTwi& twi, Ads1x14Address i2cAddress = Ads1x14Address::Default)
UrsAds1115(UrsTwi& twi, Ads1x14Address i2cAddress = Ads1x14Address::Default)

Initialisiert eine neue Instanz der Klasse.

  • twi: I²C-Interface, dass zur Datenübertragung genutzt werden soll
  • i2cAddress: Adresse unter der der ADC angesprochen werden kann.
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.

  • mux: Kanal, der gemessen werden soll.
  • ISR: Interrupt-Service-Routine, die den Interrupt behandelt.
  • alertPin: µC-Pin, an den ALERT/RDY angeschlossen ist
  • usePullUpForAlertPin: true, wenn der interne Pull-Up-Widerstand aktiviert werden soll.
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.
860 SPS  beim ADS1115
3300 SPS  beim ADS1015
Ads1x15Type getType() Liefert den ADS1x15-Typ. Ads1x15Type::ADS1015 oder Ads1x15Type::ADS1115

Implementierung

readAdcRaw

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);
}

readADCmV

float readADCmV(const Ads1x15Mux mux) { return readAdcRaw(mux) * getMilliVoltsPerBit();}

Der Rohdatenwert wird mit der aktuellen Einstellung des Verstärkers multipliziert.

startContinuousMode

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.

readContinuousModeRaw

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.

stopContinuousMode

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.

Download

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