diff --git a/src/GxEPD2_EPD.cpp b/src/GxEPD2_EPD.cpp
index 8df8bef..e9dfb19 100644
--- a/src/GxEPD2_EPD.cpp
+++ b/src/GxEPD2_EPD.cpp
@@ -17,11 +17,10 @@
 #include <avr/pgmspace.h>
 #endif
 
-GxEPD2_EPD::GxEPD2_EPD(int16_t cs, int16_t dc, int16_t rst, int16_t busy, int16_t busy_level, uint32_t busy_timeout,
+GxEPD2_EPD::GxEPD2_EPD(GxEPD2_HalInterface *hal, int16_t busy_level, uint32_t busy_timeout,
                        uint16_t w, uint16_t h, GxEPD2::Panel p, bool c, bool pu, bool fpu) :
   WIDTH(w), HEIGHT(h), panel(p), hasColor(c), hasPartialUpdate(pu), hasFastPartialUpdate(fpu),
-  _cs(cs), _dc(dc), _rst(rst), _busy(busy), _busy_level(busy_level), _busy_timeout(busy_timeout), _diag_enabled(false),
-  _pSPIx(&SPI), _spi_settings(4000000, MSBFIRST, SPI_MODE0)
+  _hal(hal), _busy_level(busy_level), _busy_timeout(busy_timeout), _diag_enabled(false)
 {
   _initial_write = true;
   _initial_refresh = true;
@@ -54,44 +53,10 @@ void GxEPD2_EPD::init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset
     Serial.begin(serial_diag_bitrate);
     _diag_enabled = true;
   }
-  if (_cs >= 0)
-  {
-    digitalWrite(_cs, HIGH); // preset (less glitch for any analyzer)
-    pinMode(_cs, OUTPUT);
-    digitalWrite(_cs, HIGH); // set (needed e.g. for RP2040)
-  }
-  if (_dc >= 0)
-  {
-    digitalWrite(_dc, HIGH); // preset (less glitch for any analyzer)
-    pinMode(_dc, OUTPUT);
-    digitalWrite(_dc, HIGH); // set (needed e.g. for RP2040)
-  }
-  _reset();
-  if (_busy >= 0)
-  {
-    pinMode(_busy, INPUT);
-  }
-  _pSPIx->begin();
-  if (_busy == MISO) // may be overridden
-  {
-    pinMode(_busy, INPUT);
-  }
-  if (_dc == MISO) // may be overridden, TTGO T5 V2.66
-  {
-    pinMode(_dc, OUTPUT);
-  }
-  if (_cs == MISO) // may be overridden
-  {
-    pinMode(_cs, INPUT);
-  }
 }
 
 void GxEPD2_EPD::end()
 {
-  _pSPIx->end();
-  if (_cs >= 0) pinMode(_cs, INPUT);
-  if (_dc >= 0) pinMode(_dc, INPUT);
-  if (_rst >= 0) pinMode(_rst, INPUT);
 }
 
 void GxEPD2_EPD::setBusyCallback(void (*busyCallback)(const void*), const void* busy_callback_parameter)
@@ -100,34 +65,27 @@ void GxEPD2_EPD::setBusyCallback(void (*busyCallback)(const void*), const void*
   _busy_callback_parameter = busy_callback_parameter;
 }
 
-void GxEPD2_EPD::selectSPI(SPIClass& spi, SPISettings spi_settings)
-{
-  _pSPIx = &spi;
-  _spi_settings = spi_settings;
-}
-
 void GxEPD2_EPD::_reset()
 {
-  if (_rst >= 0)
   {
     if (_pulldown_rst_mode)
     {
-      digitalWrite(_rst, LOW);
-      pinMode(_rst, OUTPUT);
-      digitalWrite(_rst, LOW);
+      _hal->rst(LOW);
+      _hal->rstMode(OUTPUT);
+      _hal->rst(LOW);
       delay(_reset_duration);
-      pinMode(_rst, INPUT_PULLUP);
+      _hal->rstMode(INPUT_PULLUP);
       delay(_reset_duration > 10 ? _reset_duration : 10);
     }
     else
     {
-      digitalWrite(_rst, HIGH); // NEEDED for Waveshare "clever" reset circuit, power controller before reset pulse, preset (less glitch for any analyzer)
-      pinMode(_rst, OUTPUT);
-      digitalWrite(_rst, HIGH); // NEEDED for Waveshare "clever" reset circuit, power controller before reset pulse, set (needed e.g. for RP2040)
+      _hal->rst(HIGH); // NEEDED for Waveshare "clever" reset circuit, power controller before reset pulse, preset (less glitch for any analyzer)
+      _hal->rstMode(OUTPUT);
+      _hal->rst(HIGH); // NEEDED for Waveshare "clever" reset circuit, power controller before reset pulse, set (needed e.g. for RP2040)
       delay(10); // NEEDED for Waveshare "clever" reset circuit, at least delay(2);
-      digitalWrite(_rst, LOW);
+      _hal->rst(LOW);
       delay(_reset_duration);
-      digitalWrite(_rst, HIGH);
+      _hal->rst(HIGH);
       delay(_reset_duration > 10 ? _reset_duration : 10);
     }
     _hibernating = false;
@@ -136,16 +94,15 @@ void GxEPD2_EPD::_reset()
 
 void GxEPD2_EPD::_waitWhileBusy(const char* comment, uint16_t busy_time)
 {
-  if (_busy >= 0)
   {
     delay(1); // add some margin to become active
     unsigned long start = micros();
     while (1)
     {
-      if (digitalRead(_busy) != _busy_level) break;
+      if (_hal->getBusy() != _busy_level) break;
       if (_busy_callback) _busy_callback(_busy_callback_parameter);
       else delay(1);
-      if (digitalRead(_busy) != _busy_level) break;
+      if (_hal->getBusy() != _busy_level) break;
       if (micros() - start > _busy_timeout)
       {
         Serial.println("Busy Timeout!");
@@ -169,120 +126,59 @@ void GxEPD2_EPD::_waitWhileBusy(const char* comment, uint16_t busy_time)
     }
     (void) start;
   }
-  else delay(busy_time);
 }
 
 void GxEPD2_EPD::_writeCommand(uint8_t c)
 {
-  _pSPIx->beginTransaction(_spi_settings);
-  if (_dc >= 0) digitalWrite(_dc, LOW);
-  if (_cs >= 0) digitalWrite(_cs, LOW);
-  _pSPIx->transfer(c);
-  if (_cs >= 0) digitalWrite(_cs, HIGH);
-  if (_dc >= 0) digitalWrite(_dc, HIGH);
-  _pSPIx->endTransaction();
+  _hal->writeCmd(c);
 }
 
 void GxEPD2_EPD::_writeData(uint8_t d)
 {
-  _pSPIx->beginTransaction(_spi_settings);
-  if (_cs >= 0) digitalWrite(_cs, LOW);
-  _pSPIx->transfer(d);
-  if (_cs >= 0) digitalWrite(_cs, HIGH);
-  _pSPIx->endTransaction();
+  _hal->write(d);
 }
 
 void GxEPD2_EPD::_writeData(const uint8_t* data, uint16_t n)
 {
-  _pSPIx->beginTransaction(_spi_settings);
-  if (_cs >= 0) digitalWrite(_cs, LOW);
-  for (uint16_t i = 0; i < n; i++)
-  {
-    _pSPIx->transfer(*data++);
-  }
-  if (_cs >= 0) digitalWrite(_cs, HIGH);
-  _pSPIx->endTransaction();
+  _hal->write(data, n);
 }
 
 void GxEPD2_EPD::_writeDataPGM(const uint8_t* data, uint16_t n, int16_t fill_with_zeroes)
 {
-  _pSPIx->beginTransaction(_spi_settings);
-  if (_cs >= 0) digitalWrite(_cs, LOW);
-  for (uint16_t i = 0; i < n; i++)
-  {
-    _pSPIx->transfer(pgm_read_byte(&*data++));
-  }
-  while (fill_with_zeroes > 0)
-  {
-    _pSPIx->transfer(0x00);
-    fill_with_zeroes--;
-  }
-  if (_cs >= 0) digitalWrite(_cs, HIGH);
-  _pSPIx->endTransaction();
+  _hal->write(data, n, fill_with_zeroes);
 }
 
 void GxEPD2_EPD::_writeDataPGM_sCS(const uint8_t* data, uint16_t n, int16_t fill_with_zeroes)
 {
-  _pSPIx->beginTransaction(_spi_settings);
-  for (uint8_t i = 0; i < n; i++)
-  {
-    if (_cs >= 0) digitalWrite(_cs, LOW);
-    _pSPIx->transfer(pgm_read_byte(&*data++));
-    if (_cs >= 0) digitalWrite(_cs, HIGH);
-  }
-  while (fill_with_zeroes > 0)
-  {
-    if (_cs >= 0) digitalWrite(_cs, LOW);
-    _pSPIx->transfer(0x00);
-    fill_with_zeroes--;
-    if (_cs >= 0) digitalWrite(_cs, HIGH);
+  _hal->write(data, n);
+  if (fill_with_zeroes > 0) {
+    uint8_t buf[fill_with_zeroes];
+    memset(buf, 0, fill_with_zeroes);
+    _hal->write(buf, fill_with_zeroes);
   }
-  _pSPIx->endTransaction();
 }
 
 void GxEPD2_EPD::_writeCommandData(const uint8_t* pCommandData, uint8_t datalen)
 {
-  _pSPIx->beginTransaction(_spi_settings);
-  if (_dc >= 0) digitalWrite(_dc, LOW);
-  if (_cs >= 0) digitalWrite(_cs, LOW);
-  _pSPIx->transfer(*pCommandData++);
-  if (_dc >= 0) digitalWrite(_dc, HIGH);
-  for (uint8_t i = 0; i < datalen - 1; i++)  // sub the command
-  {
-    _pSPIx->transfer(*pCommandData++);
-  }
-  if (_cs >= 0) digitalWrite(_cs, HIGH);
-  _pSPIx->endTransaction();
+  _hal->writeCmd(pCommandData, datalen, false);
 }
 
 void GxEPD2_EPD::_writeCommandDataPGM(const uint8_t* pCommandData, uint8_t datalen)
 {
-  _pSPIx->beginTransaction(_spi_settings);
-  if (_dc >= 0) digitalWrite(_dc, LOW);
-  if (_cs >= 0) digitalWrite(_cs, LOW);
-  _pSPIx->transfer(pgm_read_byte(&*pCommandData++));
-  if (_dc >= 0) digitalWrite(_dc, HIGH);
-  for (uint8_t i = 0; i < datalen - 1; i++)  // sub the command
-  {
-    _pSPIx->transfer(pgm_read_byte(&*pCommandData++));
-  }
-  if (_cs >= 0) digitalWrite(_cs, HIGH);
-  _pSPIx->endTransaction();
+  _hal->writeCmd(pCommandData, datalen, true);
 }
 
 void GxEPD2_EPD::_startTransfer()
 {
-  _pSPIx->beginTransaction(_spi_settings);
-  if (_cs >= 0) digitalWrite(_cs, LOW);
+  _hal->startTransfer();
 }
 
 void GxEPD2_EPD::_transfer(uint8_t value)
 {
-  _pSPIx->transfer(value);
+  _hal->transfer(value);
 }
 
 void GxEPD2_EPD::_endTransfer()
 {
-  if (_cs >= 0) digitalWrite(_cs, HIGH);
-  _pSPIx->endTransaction();
+  _hal->endTransfer();
 }
diff --git a/src/GxEPD2_EPD.h b/src/GxEPD2_EPD.h
index 34c1145..1e8ea64 100644
--- a/src/GxEPD2_EPD.h
+++ b/src/GxEPD2_EPD.h
@@ -13,9 +13,9 @@
 #define _GxEPD2_EPD_H_
 
 #include <Arduino.h>
-#include <SPI.h>
 
 #include <GxEPD2.h>
+#include <GxEPD2_Hal.h>
 
 #pragma GCC diagnostic ignored "-Wunused-parameter"
 //#pragma GCC diagnostic ignored "-Wsign-compare"
@@ -31,7 +31,7 @@ class GxEPD2_EPD
     const bool hasPartialUpdate;
     const bool hasFastPartialUpdate;
     // constructor
-    GxEPD2_EPD(int16_t cs, int16_t dc, int16_t rst, int16_t busy, int16_t busy_level, uint32_t busy_timeout,
+    GxEPD2_EPD(GxEPD2_HalInterface *hal, int16_t busy_level, uint32_t busy_timeout,
                uint16_t w, uint16_t h, GxEPD2::Panel p, bool c, bool pu, bool fpu);
     virtual void init(uint32_t serial_diag_bitrate = 0); // serial_diag_bitrate = 0 : disabled
     virtual void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration = 10, bool pulldown_rst_mode = false);
@@ -97,7 +97,6 @@ class GxEPD2_EPD
     {
       return (a > b ? a : b);
     };
-    void selectSPI(SPIClass& spi, SPISettings spi_settings);
   protected:
     void _reset();
     void _waitWhileBusy(const char* comment = 0, uint16_t busy_time = 5000);
@@ -112,16 +111,15 @@ class GxEPD2_EPD
     void _transfer(uint8_t value);
     void _endTransfer();
   protected:
-    int16_t _cs, _dc, _rst, _busy, _busy_level;
+    GxEPD2_HalInterface *_hal;
+    int16_t _busy_level;
     uint32_t _busy_timeout;
     bool _diag_enabled, _pulldown_rst_mode;
-    SPIClass* _pSPIx;
-    SPISettings _spi_settings;
     bool _initial_write, _initial_refresh;
     bool _power_is_on, _using_partial_mode, _hibernating;
     bool _init_display_done;
     uint16_t _reset_duration;
-    void (*_busy_callback)(const void*); 
+    void (*_busy_callback)(const void*);
     const void* _busy_callback_parameter;
 };
 
diff --git a/src/GxEPD2_Hal.h b/src/GxEPD2_Hal.h
new file mode 100644
index 0000000..13424b6
--- /dev/null
+++ b/src/GxEPD2_Hal.h
@@ -0,0 +1,19 @@
+#pragma once
+
+class GxEPD2_HalInterface {
+    public:
+        virtual void rstMode(uint8_t mode) = 0;
+        virtual void rst(bool level) = 0;
+        virtual int getBusy(void) = 0;
+        virtual bool isRst(void) = 0;
+
+        virtual void write(uint8_t buf) = 0;
+        virtual void write(const uint8_t *buf, uint16_t n) = 0;
+        virtual void write(const uint8_t *buf, uint16_t n, int16_t fill_with_zeroes) = 0;
+        virtual void writeCmd(const uint8_t val) = 0;
+        virtual void writeCmd(const uint8_t* pCommandData, uint8_t datalen, bool isPGM) = 0;
+
+        virtual void startTransfer(void) = 0;
+        virtual void endTransfer(void) = 0;
+        virtual void transfer(const uint8_t val) = 0;
+};
diff --git a/src/epd/GxEPD2_150_BN.cpp b/src/epd/GxEPD2_150_BN.cpp
index bfb3ddf..dba3d78 100644
--- a/src/epd/GxEPD2_150_BN.cpp
+++ b/src/epd/GxEPD2_150_BN.cpp
@@ -14,8 +14,8 @@
 
 #include "GxEPD2_150_BN.h"
 
-GxEPD2_150_BN::GxEPD2_150_BN(int16_t cs, int16_t dc, int16_t rst, int16_t busy) :
-  GxEPD2_EPD(cs, dc, rst, busy, HIGH, 10000000, WIDTH, HEIGHT, panel, hasColor, hasPartialUpdate, hasFastPartialUpdate)
+GxEPD2_150_BN::GxEPD2_150_BN(GxEPD2_HalInterface *hal) :
+  GxEPD2_EPD(hal, HIGH, 10000000, WIDTH, HEIGHT, panel, hasColor, hasPartialUpdate, hasFastPartialUpdate)
 {
 }
 
@@ -269,7 +269,7 @@ void GxEPD2_150_BN::refresh(int16_t x, int16_t y, int16_t w, int16_t h)
   int16_t y1 = y < 0 ? 0 : y; // limit
   w1 = x1 + w1 < int16_t(WIDTH) ? w1 : int16_t(WIDTH) - x1; // limit
   h1 = y1 + h1 < int16_t(HEIGHT) ? h1 : int16_t(HEIGHT) - y1; // limit
-  if ((w1 <= 0) || (h1 <= 0)) return; 
+  if ((w1 <= 0) || (h1 <= 0)) return;
   // make x1, w1 multiple of 8
   w1 += x1 % 8;
   if (w1 % 8 > 0) w1 += 8 - w1 % 8;
@@ -287,7 +287,7 @@ void GxEPD2_150_BN::powerOff()
 void GxEPD2_150_BN::hibernate()
 {
   _PowerOff();
-  if (_rst >= 0)
+  if (_hal->isRst())
   {
     _writeCommand(0x10); // deep sleep mode
     _writeData(0x1);     // enter deep sleep
diff --git a/src/epd/GxEPD2_150_BN.h b/src/epd/GxEPD2_150_BN.h
index bc46a45..954b9c4 100644
--- a/src/epd/GxEPD2_150_BN.h
+++ b/src/epd/GxEPD2_150_BN.h
@@ -16,6 +16,7 @@
 #define _GxEPD2_150_BN_H_
 
 #include "../GxEPD2_EPD.h"
+#include "../GxEPD2_Hal.h"
 
 class GxEPD2_150_BN : public GxEPD2_EPD
 {
@@ -33,7 +34,7 @@ class GxEPD2_150_BN : public GxEPD2_EPD
     static const uint16_t full_refresh_time = 4000; // ms, e.g. 3825000us
     static const uint16_t partial_refresh_time = 800; // ms, e.g. 736000us
     // constructor
-    GxEPD2_150_BN(int16_t cs, int16_t dc, int16_t rst, int16_t busy);
+    GxEPD2_150_BN(GxEPD2_HalInterface *hal);
     // methods (virtual)
     //  Support for Bitmaps (Sprites) to Controller Buffer and to Screen
     void clearScreen(uint8_t value = 0xFF); // init controller memory and screen (default white)