diff --git a/src/libDevCom/ADS1015.cpp b/src/libDevCom/ADS1015.cpp
index 8ceb540f44f276d920870cd28dee97e5b8116c1b..06b77d482e22c830a531d5232e399e92a15282f0 100644
--- a/src/libDevCom/ADS1015.cpp
+++ b/src/libDevCom/ADS1015.cpp
@@ -89,8 +89,8 @@ int32_t ADS1015::readCount(uint8_t channel) {
 
     // write the configuration
     m_com->write_reg16(static_cast<uint16_t>(Address::POINTER_CONFIG), config);
-    std::this_thread::sleep_for(std::chrono::microseconds(
-        1000));  // wait for conversion delay (plus some headroom)
+    while (!conversionComplete()) {
+    }
 
     uint16_t result =
         m_com->read_reg16(static_cast<uint16_t>(Address::POINTER_CONVERT));
@@ -224,3 +224,84 @@ void ADS1015::setFullScaleRange(double val) {
     m_calibration = std::make_shared<LinearCalibration>(val, m_maxValue);
     this->setCalibration(m_calibration);
 }
+
+double ADS1015::readDifferentialP0N1() {
+    return readDifferential(DifferentialConfig::P0_N1);
+}
+
+double ADS1015::readDifferentialP2N3() {
+    return readDifferential(DifferentialConfig::P2_N3);
+}
+
+double ADS1015::readDifferential(DifferentialConfig diff_config) {
+    int16_t counts = readCountDifferential(diff_config);
+    double multiplier = m_fullscale_range / m_maxValue;
+    logger(logINFO) << __PRETTY_FUNCTION__ << " counts = " << counts
+                    << ", multiplier = " << multiplier;
+    return counts * multiplier;
+}
+
+int16_t ADS1015::readCountDifferentialP0N1() {
+    return readCountDifferential(DifferentialConfig::P0_N1);
+}
+
+int16_t ADS1015::readCountDifferentialP2N3() {
+    return readCountDifferential(DifferentialConfig::P2_N3);
+}
+
+int16_t ADS1015::readCountDifferential(DifferentialConfig diff_config) {
+    uint16_t config = 0;
+
+    // set single shot conifguration
+    setSingleShot(config);
+
+    // set the programmable gain amplifier configuration
+    setPGA(config);
+
+    // set the differential channel mux
+    switch (diff_config) {
+        case DifferentialConfig::P0_N1:
+            config |= static_cast<uint16_t>(Config::CONFIG_MUX_DIFF_P0_N1);
+            break;
+        case DifferentialConfig::P2_N3:
+            config |= static_cast<uint16_t>(Config::CONFIG_MUX_DIFF_P2_N3);
+            break;
+        default:
+            throw std::runtime_error(
+                "Invalid differential channel config provided for ADS1015 "
+                "device");
+            break;
+    }
+
+    // set the 'start signle-conversion' bit
+    setStartConversion(config);
+
+    // write the configuration
+    m_com->write_reg16(static_cast<uint16_t>(Address::POINTER_CONFIG), config);
+    while (!conversionComplete()) {
+    }
+
+    uint16_t raw_result =
+        m_com->read_reg16(static_cast<uint16_t>(Address::POINTER_CONVERT));
+
+    logger(logINFO) << __PRETTY_FUNCTION__ << " raw_result = " << raw_result;
+    // ADS1015 writes 12-bit results shifted by four, so remove the shift
+    raw_result = raw_result >> 4;
+    logger(logINFO) << __PRETTY_FUNCTION__
+                    << " shifted raw_result = " << raw_result;
+
+    // differential measurements are signed, so check the sign bit
+    if (raw_result > 0x07FF) {
+        // negative number: set the sign bit
+        raw_result |= 0xF000;
+    }
+
+    // return the signed interpretation
+    return static_cast<int16_t>(raw_result);
+}
+
+bool ADS1015::conversionComplete() {
+    uint16_t reg =
+        m_com->read_reg16(static_cast<uint16_t>(Address::POINTER_CONFIG));
+    return (reg & 0x8000) != 0;
+}
diff --git a/src/libDevCom/ADS1015.h b/src/libDevCom/ADS1015.h
index 4b104c54142b35a547ae1788bb412d66962a9cac..02682d5baf5d0c5b527f81604b558d3e5b5161e9 100644
--- a/src/libDevCom/ADS1015.h
+++ b/src/libDevCom/ADS1015.h
@@ -14,8 +14,11 @@ class I2CCom;
  *
  * The `ADS1015` class provides a driver for the Texas Instruments low-power,
  * I2C compatible 4-channel 12-bit ADC. Support for single-shot ADC conversions
- * is supported, whereas the differential measurement capabilities of the
- * ADS1015 are not.
+ * is supported across all four channels in single-ended measurement mode.
+ * Support for differential measurements are supported in the following
+ * configuration:
+ *    - Across channel 0 and 1: channel 0 as AINP and channel 1 as AINN
+ *    - Across channel 2 and 3: channel 2 as AINP and channel 3 as AINN
  *
  * [Datasheet](https://cdn.sparkfun.com/assets/a/c/4/9/b/ads1015.pdf)
  */
@@ -85,6 +88,22 @@ class ADS1015 : public ADCDevice {
      */
     void setFullScaleRange(double val);
 
+    //! \brief Return the differential measurement with channel 0 as AINP and
+    //! channel 1 as AINN
+    int16_t readCountDifferentialP0N1();
+
+    //! \brief Return the differential measurement with channel 2 as AINP and
+    //! channel 3 as AINN
+    int16_t readCountDifferentialP2N3();
+
+    //! \brief Return the differential measurement between channel 0 (AINP) and
+    //! channel 1 (AINN) converted to Volts
+    double readDifferentialP0N1();
+
+    //! \brief Return the differential measurement between channel 2 (AINP) and
+    //! channel 3 (AINN) converted to Volts)
+    double readDifferentialP2N3();
+
  private:
     void init(double full_scale_range, std::shared_ptr<I2CCom> com);
 
@@ -156,6 +175,15 @@ class ADS1015 : public ADCDevice {
                                    // in high state (default)
     };
 
+    // The ADS1015 can be configured for differential measurements with the
+    // negative channel being either channel 1 or channel 3, however it is
+    // recommended to only perform differential measurements between the
+    // adjacent channels due to the internal capacitive network. This enum
+    // defines the possible differential measurement configuration, but is not
+    // known to the user which will call the more direct `readDifferentialP0N1`
+    // and `readDifferentialP2N3` methods.
+    enum class DifferentialConfig { P0_N1, P2_N3 };
+
     // map between programmable gain configuration and ADS1015 full scale range
     // in volts
     const std::map<Gain, std::string> gainToFSRMap{
@@ -185,6 +213,7 @@ class ADS1015 : public ADCDevice {
     double m_fullscale_range;  // volts
 
     // some helper methods
+    bool conversionComplete();
     Gain fsrToGain(double fsr);
     std::string fsrToString();
     std::string fsrValToString(double val);
@@ -195,6 +224,10 @@ class ADS1015 : public ADCDevice {
     void setPGA(uint16_t& config);
     void setSingleChannel(uint16_t& config, uint8_t channel);
     void setStartConversion(uint16_t& config);
+
+    // methods for performing differential measurement
+    int16_t readCountDifferential(DifferentialConfig config);
+    double readDifferential(DifferentialConfig config);
 };
 
 #endif  // DEVCOM_ADS1015_H