diff --git a/RF24.cpp b/RF24.cpp
index 9e5b4a8..af00758 100644
--- a/RF24.cpp
+++ b/RF24.cpp
@@ -12,228 +12,24 @@
 
 /****************************************************************************/
 
-void RF24::csn(bool mode)
-{
-#if defined(RF24_TINY)
-    if (ce_pin != csn_pin) {
-        digitalWrite(csn_pin, mode);
-    }
-    else {
-        if (mode == HIGH) {
-            PORTB |= (1 << PINB2);                         // SCK->CSN HIGH
-            delayMicroseconds(RF24_CSN_SETTLE_HIGH_DELAY); // allow csn to settle.
-        }
-        else {
-            PORTB &= ~(1 << PINB2);                       // SCK->CSN LOW
-            delayMicroseconds(RF24_CSN_SETTLE_LOW_DELAY); // allow csn to settle
-        }
-    }
-    // Return, CSN toggle complete
-    return;
-
-#elif defined(ARDUINO) && !defined(RF24_SPI_TRANSACTIONS)
-    // Minimum ideal SPI bus speed is 2x data rate
-    // If we assume 2Mbs data rate and 16Mhz clock, a
-    // divider of 4 is the minimum we want.
-    // CLK:BUS 8Mhz:2Mhz, 16Mhz:4Mhz, or 20Mhz:5Mhz
-
-    #if !defined(SOFTSPI)
-        // applies to SPI_UART and inherent hardware SPI
-        #if defined(RF24_SPI_PTR)
-    _spi->setBitOrder(MSBFIRST);
-    _spi->setDataMode(SPI_MODE0);
-
-            #if !defined(F_CPU) || F_CPU < 20000000
-    _spi->setClockDivider(SPI_CLOCK_DIV2);
-            #elif F_CPU < 40000000
-    _spi->setClockDivider(SPI_CLOCK_DIV4);
-            #elif F_CPU < 80000000
-    _spi->setClockDivider(SPI_CLOCK_DIV8);
-            #elif F_CPU < 160000000
-    _spi->setClockDivider(SPI_CLOCK_DIV16);
-            #elif F_CPU < 320000000
-    _spi->setClockDivider(SPI_CLOCK_DIV32);
-            #elif F_CPU < 640000000
-    _spi->setClockDivider(SPI_CLOCK_DIV64);
-            #elif F_CPU < 1280000000
-    _spi->setClockDivider(SPI_CLOCK_DIV128);
-            #else // F_CPU >= 1280000000
-                #error "Unsupported CPU frequency. Please set correct SPI divider."
-            #endif // F_CPU to SPI_CLOCK_DIV translation
-
-        #else // !defined(RF24_SPI_PTR)
-    _SPI.setBitOrder(MSBFIRST);
-    _SPI.setDataMode(SPI_MODE0);
-
-            #if !defined(F_CPU) || F_CPU < 20000000
-    _SPI.setClockDivider(SPI_CLOCK_DIV2);
-            #elif F_CPU < 40000000
-    _SPI.setClockDivider(SPI_CLOCK_DIV4);
-            #elif F_CPU < 80000000
-    _SPI.setClockDivider(SPI_CLOCK_DIV8);
-            #elif F_CPU < 160000000
-    _SPI.setClockDivider(SPI_CLOCK_DIV16);
-            #elif F_CPU < 320000000
-    _SPI.setClockDivider(SPI_CLOCK_DIV32);
-            #elif F_CPU < 640000000
-    _SPI.setClockDivider(SPI_CLOCK_DIV64);
-            #elif F_CPU < 1280000000
-    _SPI.setClockDivider(SPI_CLOCK_DIV128);
-            #else // F_CPU >= 1280000000
-                #error "Unsupported CPU frequency. Please set correct SPI divider."
-            #endif // F_CPU to SPI_CLOCK_DIV translation
-        #endif     // !defined(RF24_SPI_PTR)
-    #endif         // !defined(SOFTSPI)
-
-#elif defined(RF24_RPi)
-    if (!mode)
-        _SPI.chipSelect(csn_pin);
-#endif // defined(RF24_RPi)
-
-#if !defined(RF24_LINUX)
-    digitalWrite(csn_pin, mode);
-    delayMicroseconds(csDelay);
-#else
-    static_cast<void>(mode); // ignore -Wunused-parameter
-#endif // !defined(RF24_LINUX)
-}
-
-/****************************************************************************/
-
 void RF24::ce(bool level)
 {
-#ifndef RF24_LINUX
-    //Allow for 3-pin use on ATTiny
-    if (ce_pin != csn_pin) {
-#endif
-        digitalWrite(ce_pin, level);
-#ifndef RF24_LINUX
-    }
-#endif
-}
-
-/****************************************************************************/
-
-inline void RF24::beginTransaction()
-{
-#if defined(RF24_SPI_TRANSACTIONS)
-    #if defined(RF24_SPI_PTR)
-        #if defined(RF24_RP2)
-    _spi->beginTransaction(spi_speed);
-        #else  // ! defined (RF24_RP2)
-    _spi->beginTransaction(SPISettings(spi_speed, MSBFIRST, SPI_MODE0));
-        #endif // ! defined (RF24_RP2)
-    #else      // !defined(RF24_SPI_PTR)
-    _SPI.beginTransaction(SPISettings(spi_speed, MSBFIRST, SPI_MODE0));
-    #endif     // !defined(RF24_SPI_PTR)
-#endif         // defined (RF24_SPI_TRANSACTIONS)
-    csn(LOW);
-}
-
-/****************************************************************************/
-
-inline void RF24::endTransaction()
-{
-    csn(HIGH);
-#if defined(RF24_SPI_TRANSACTIONS)
-    #if defined(RF24_SPI_PTR)
-    _spi->endTransaction();
-    #else  // !defined(RF24_SPI_PTR)
-    _SPI.endTransaction();
-    #endif // !defined(RF24_SPI_PTR)
-#endif     // defined (RF24_SPI_TRANSACTIONS)
+    hal->ce(level);
 }
 
 /****************************************************************************/
 
 void RF24::read_register(uint8_t reg, uint8_t* buf, uint8_t len)
 {
-#if defined(RF24_LINUX) || defined(RF24_RP2)
-    beginTransaction(); //configures the spi settings for RPi, locks mutex and setting csn low
-    uint8_t* prx = spi_rxbuff;
-    uint8_t* ptx = spi_txbuff;
-    uint8_t size = static_cast<uint8_t>(len + 1); // Add register value to transmit buffer
-
-    *ptx++ = (R_REGISTER | reg);
-
-    while (len--) {
-        *ptx++ = RF24_NOP; // Dummy operation, just for reading
-    }
-
-    #if defined(RF24_RP2)
-    _spi->transfernb((const uint8_t*)spi_txbuff, spi_rxbuff, size);
-    #else  // !defined (RF24_RP2)
-    _SPI.transfernb(reinterpret_cast<char*>(spi_txbuff), reinterpret_cast<char*>(spi_rxbuff), size);
-    #endif // !defined (RF24_RP2)
-
-    status = *prx++; // status is 1st byte of receive buffer
-
-    // decrement before to skip status byte
-    while (--size) {
-        *buf++ = *prx++;
-    }
-
-    endTransaction(); // unlocks mutex and setting csn high
-
-#else // !defined(RF24_LINUX) && !defined(RF24_RP2)
-
-    beginTransaction();
-    #if defined(RF24_SPI_PTR)
-    status = _spi->transfer(R_REGISTER | reg);
-    while (len--) {
-        *buf++ = _spi->transfer(0xFF);
-    }
-
-    #else // !defined(RF24_SPI_PTR)
-    status = _SPI.transfer(R_REGISTER | reg);
-    while (len--) {
-        *buf++ = _SPI.transfer(0xFF);
-    }
-
-    #endif // !defined(RF24_SPI_PTR)
-    endTransaction();
-#endif     // !defined(RF24_LINUX) && !defined(RF24_RP2)
+    status = hal->read(R_REGISTER | reg, buf, len);
 }
 
 /****************************************************************************/
 
 uint8_t RF24::read_register(uint8_t reg)
 {
-    uint8_t result;
-
-#if defined(RF24_LINUX) || defined(RF24_RP2)
-    beginTransaction();
-
-    uint8_t* prx = spi_rxbuff;
-    uint8_t* ptx = spi_txbuff;
-    *ptx++ = (R_REGISTER | reg);
-    *ptx++ = RF24_NOP; // Dummy operation, just for reading
-
-    #if defined(RF24_RP2)
-    _spi->transfernb((const uint8_t*)spi_txbuff, spi_rxbuff, 2);
-    #else  // !defined(RF24_RP2)
-    _SPI.transfernb(reinterpret_cast<char*>(spi_txbuff), reinterpret_cast<char*>(spi_rxbuff), 2);
-    #endif // !defined(RF24_RP2)
-
-    status = *prx;   // status is 1st byte of receive buffer
-    result = *++prx; // result is 2nd byte of receive buffer
-
-    endTransaction();
-#else // !defined(RF24_LINUX) && !defined(RF24_RP2)
-
-    beginTransaction();
-    #if defined(RF24_SPI_PTR)
-    status = _spi->transfer(R_REGISTER | reg);
-    result = _spi->transfer(0xff);
-
-    #else // !defined(RF24_SPI_PTR)
-    status = _SPI.transfer(R_REGISTER | reg);
-    result = _SPI.transfer(0xff);
-
-    #endif // !defined(RF24_SPI_PTR)
-    endTransaction();
-#endif     // !defined(RF24_LINUX) && !defined(RF24_RP2)
-
+    uint8_t result = 0xff;
+    status = hal->read(R_REGISTER | reg, &result, sizeof(result));
     return result;
 }
 
@@ -241,43 +37,7 @@ uint8_t RF24::read_register(uint8_t reg)
 
 void RF24::write_register(uint8_t reg, const uint8_t* buf, uint8_t len)
 {
-#if defined(RF24_LINUX) || defined(RF24_RP2)
-    beginTransaction();
-    uint8_t* prx = spi_rxbuff;
-    uint8_t* ptx = spi_txbuff;
-    uint8_t size = static_cast<uint8_t>(len + 1); // Add register value to transmit buffer
-
-    *ptx++ = (W_REGISTER | (REGISTER_MASK & reg));
-    while (len--) {
-        *ptx++ = *buf++;
-    }
-
-    #if defined(RF24_RP2)
-    _spi->transfernb((const uint8_t*)spi_txbuff, spi_rxbuff, size);
-    #else  // !defined(RF24_RP2)
-    _SPI.transfernb(reinterpret_cast<char*>(spi_txbuff), reinterpret_cast<char*>(spi_rxbuff), size);
-    #endif // !defined(RF24_RP2)
-
-    status = *prx; // status is 1st byte of receive buffer
-    endTransaction();
-#else // !defined(RF24_LINUX) && !defined(RF24_RP2)
-
-    beginTransaction();
-    #if defined(RF24_SPI_PTR)
-    status = _spi->transfer(W_REGISTER | reg);
-    while (len--) {
-        _spi->transfer(*buf++);
-    }
-
-    #else // !defined(RF24_SPI_PTR)
-    status = _SPI.transfer(W_REGISTER | reg);
-    while (len--) {
-        _SPI.transfer(*buf++);
-    }
-
-    #endif // !defined(RF24_SPI_PTR)
-    endTransaction();
-#endif     // !defined(RF24_LINUX) && !defined(RF24_RP2)
+    status = hal->write(W_REGISTER | reg, buf, len);
 }
 
 /****************************************************************************/
@@ -288,47 +48,11 @@ void RF24::write_register(uint8_t reg, uint8_t value, bool is_cmd_only)
         if (reg != RF24_NOP) { // don't print the get_status() operation
             IF_SERIAL_DEBUG(printf_P(PSTR("write_register(%02x)\r\n"), reg));
         }
-        beginTransaction();
-#if defined(RF24_LINUX)
-        status = _SPI.transfer(W_REGISTER | reg);
-#else // !defined(RF24_LINUX) || defined (RF24_RP2)
-    #if defined(RF24_SPI_PTR)
-        status = _spi->transfer(W_REGISTER | reg);
-    #else  // !defined (RF24_SPI_PTR)
-        status = _SPI.transfer(W_REGISTER | reg);
-    #endif // !defined (RF24_SPI_PTR)
-#endif     // !defined(RF24_LINUX) || defined(RF24_RP2)
-        endTransaction();
+        status = hal->write(W_REGISTER | reg, nullptr, 0);
     }
     else {
         IF_SERIAL_DEBUG(printf_P(PSTR("write_register(%02x,%02x)\r\n"), reg, value));
-#if defined(RF24_LINUX) || defined(RF24_RP2)
-        beginTransaction();
-        uint8_t* prx = spi_rxbuff;
-        uint8_t* ptx = spi_txbuff;
-        *ptx++ = (W_REGISTER | reg);
-        *ptx = value;
-
-    #if defined(RF24_RP2)
-        _spi->transfernb((const uint8_t*)spi_txbuff, spi_rxbuff, 2);
-    #else  // !defined(RF24_RP2)
-        _SPI.transfernb(reinterpret_cast<char*>(spi_txbuff), reinterpret_cast<char*>(spi_rxbuff), 2);
-    #endif // !defined(RF24_RP2)
-
-        status = *prx++; // status is 1st byte of receive buffer
-        endTransaction();
-#else // !defined(RF24_LINUX) && !defined(RF24_RP2)
-
-        beginTransaction();
-    #if defined(RF24_SPI_PTR)
-        status = _spi->transfer(W_REGISTER | reg);
-        _spi->transfer(value);
-    #else  // !defined(RF24_SPI_PTR)
-        status = _SPI.transfer(W_REGISTER | reg);
-        _SPI.transfer(value);
-    #endif // !defined(RF24_SPI_PTR)
-        endTransaction();
-#endif     // !defined(RF24_LINUX) && !defined(RF24_RP2)
+        status = hal->write(W_REGISTER | reg, &value, sizeof(value));
     }
 }
 
@@ -347,60 +71,8 @@ void RF24::write_payload(const void* buf, uint8_t data_len, const uint8_t writeT
         data_len = rf24_min(data_len, static_cast<uint8_t>(32));
     }
 
-    //printf("[Writing %u bytes %u blanks]",data_len,blank_len);
     IF_SERIAL_DEBUG(printf("[Writing %u bytes %u blanks]\n", data_len, blank_len););
-
-#if defined(RF24_LINUX) || defined(RF24_RP2)
-    beginTransaction();
-    uint8_t* prx = spi_rxbuff;
-    uint8_t* ptx = spi_txbuff;
-    uint8_t size;
-    size = static_cast<uint8_t>(data_len + blank_len + 1); // Add register value to transmit buffer
-
-    *ptx++ = writeType;
-    while (data_len--) {
-        *ptx++ = *current++;
-    }
-
-    while (blank_len--) {
-        *ptx++ = 0;
-    }
-
-    #if defined(RF24_RP2)
-    _spi->transfernb((const uint8_t*)spi_txbuff, spi_rxbuff, size);
-    #else  // !defined(RF24_RP2)
-    _SPI.transfernb(reinterpret_cast<char*>(spi_txbuff), reinterpret_cast<char*>(spi_rxbuff), size);
-    #endif // !defined(RF24_RP2)
-
-    status = *prx; // status is 1st byte of receive buffer
-    endTransaction();
-
-#else // !defined(RF24_LINUX) && !defined(RF24_RP2)
-
-    beginTransaction();
-    #if defined(RF24_SPI_PTR)
-    status = _spi->transfer(writeType);
-    while (data_len--) {
-        _spi->transfer(*current++);
-    }
-
-    while (blank_len--) {
-        _spi->transfer(0);
-    }
-
-    #else // !defined(RF24_SPI_PTR)
-    status = _SPI.transfer(writeType);
-    while (data_len--) {
-        _SPI.transfer(*current++);
-    }
-
-    while (blank_len--) {
-        _SPI.transfer(0);
-    }
-
-    #endif // !defined(RF24_SPI_PTR)
-    endTransaction();
-#endif     // !defined(RF24_LINUX) && !defined(RF24_RP2)
+    status = hal->write(writeType, current, data_len, blank_len);
 }
 
 /****************************************************************************/
@@ -421,65 +93,7 @@ void RF24::read_payload(void* buf, uint8_t data_len)
     //printf("[Reading %u bytes %u blanks]",data_len,blank_len);
 
     IF_SERIAL_DEBUG(printf("[Reading %u bytes %u blanks]\n", data_len, blank_len););
-
-#if defined(RF24_LINUX) || defined(RF24_RP2)
-    beginTransaction();
-    uint8_t* prx = spi_rxbuff;
-    uint8_t* ptx = spi_txbuff;
-    uint8_t size;
-    size = static_cast<uint8_t>(data_len + blank_len + 1); // Add register value to transmit buffer
-
-    *ptx++ = R_RX_PAYLOAD;
-    while (--size) {
-        *ptx++ = RF24_NOP;
-    }
-
-    size = static_cast<uint8_t>(data_len + blank_len + 1); // Size has been lost during while, re affect
-
-    #if defined(RF24_RP2)
-    _spi->transfernb((const uint8_t*)spi_txbuff, spi_rxbuff, size);
-    #else  // !defined(RF24_RP2)
-    _SPI.transfernb(reinterpret_cast<char*>(spi_txbuff), reinterpret_cast<char*>(spi_rxbuff), size);
-    #endif // !defined(RF24_RP2)
-
-    status = *prx++; // 1st byte is status
-
-    if (data_len > 0) {
-        // Decrement before to skip 1st status byte
-        while (--data_len) {
-            *current++ = *prx++;
-        }
-
-        *current = *prx;
-    }
-    endTransaction();
-#else // !defined(RF24_LINUX) && !defined(RF24_RP2)
-
-    beginTransaction();
-    #if defined(RF24_SPI_PTR)
-    status = _spi->transfer(R_RX_PAYLOAD);
-    while (data_len--) {
-        *current++ = _spi->transfer(0xFF);
-    }
-
-    while (blank_len--) {
-        _spi->transfer(0xFF);
-    }
-
-    #else // !defined(RF24_SPI_PTR)
-    status = _SPI.transfer(R_RX_PAYLOAD);
-    while (data_len--) {
-        *current++ = _SPI.transfer(0xFF);
-    }
-
-    while (blank_len--) {
-        _SPI.transfer(0xff);
-    }
-
-    #endif // !defined(RF24_SPI_PTR)
-    endTransaction();
-
-#endif // !defined(RF24_LINUX) && !defined(RF24_RP2)
+    status = hal->read(R_RX_PAYLOAD, current, data_len, blank_len);
 }
 
 /****************************************************************************/
@@ -577,16 +191,16 @@ uint8_t RF24::sprintf_address_register(char* out_buffer, uint8_t reg, uint8_t qt
 
 /****************************************************************************/
 
-RF24::RF24(rf24_gpio_pin_t _cepin, rf24_gpio_pin_t _cspin, uint32_t _spi_speed)
-    : ce_pin(_cepin), csn_pin(_cspin), spi_speed(_spi_speed), payload_size(32), _is_p_variant(false), _is_p0_rx(false), addr_width(5), dynamic_payloads_enabled(true), csDelay(5)
+RF24::RF24(RF24_hal* _hal)
+    : hal(_hal), payload_size(32), _is_p_variant(false), _is_p0_rx(false), addr_width(5), dynamic_payloads_enabled(true), csDelay(5)
 {
     _init_obj();
 }
 
 /****************************************************************************/
 
-RF24::RF24(uint32_t _spi_speed)
-    : ce_pin(RF24_PIN_INVALID), csn_pin(RF24_PIN_INVALID), spi_speed(_spi_speed), payload_size(32), _is_p_variant(false), _is_p0_rx(false), addr_width(5), dynamic_payloads_enabled(true), csDelay(5)
+RF24::RF24()
+    : hal(nullptr), payload_size(32), _is_p_variant(false), _is_p0_rx(false), addr_width(5), dynamic_payloads_enabled(true), csDelay(5)
 {
     _init_obj();
 }
@@ -595,16 +209,7 @@ RF24::RF24(uint32_t _spi_speed)
 
 void RF24::_init_obj()
 {
-    // Use a pointer on the Arduino platform
-
-#if defined(RF24_SPI_PTR) && !defined(RF24_RP2)
-    _spi = &SPI;
-#endif // defined (RF24_SPI_PTR)
-
     pipe0_reading_address[0] = 0;
-    if (spi_speed <= 35000) { //Handle old BCM2835 speed constants, default to RF24_SPI_SPEED
-        spi_speed = RF24_SPI_SPEED;
-    }
 }
 
 /****************************************************************************/
@@ -677,19 +282,6 @@ static const PROGMEM char* const rf24_pa_dbm_e_str_P[] = {
     rf24_pa_dbm_e_str_3,
 };
 
-    #if defined(RF24_LINUX)
-static const char rf24_csn_e_str_0[] = "CE0 (PI Hardware Driven)";
-static const char rf24_csn_e_str_1[] = "CE1 (PI Hardware Driven)";
-static const char rf24_csn_e_str_2[] = "CE2 (PI Hardware Driven)";
-static const char rf24_csn_e_str_3[] = "Custom GPIO Software Driven";
-static const char* const rf24_csn_e_str_P[] = {
-    rf24_csn_e_str_0,
-    rf24_csn_e_str_1,
-    rf24_csn_e_str_2,
-    rf24_csn_e_str_3,
-};
-    #endif // defined(RF24_LINUX)
-
 static const PROGMEM char rf24_feature_e_str_on[] = "= Enabled";
 static const PROGMEM char rf24_feature_e_str_allowed[] = "= Allowed";
 static const PROGMEM char rf24_feature_e_str_open[] = " open ";
@@ -704,19 +296,6 @@ static const PROGMEM char* const rf24_feature_e_str_P[] = {
 
 void RF24::printDetails(void)
 {
-
-    #if defined(RF24_LINUX)
-    printf("================ SPI Configuration ================\n");
-    uint8_t bus_ce = static_cast<uint8_t>(csn_pin % 10);
-    uint8_t bus_numb = static_cast<uint8_t>((csn_pin - bus_ce) / 10);
-    printf("CSN Pin\t\t= /dev/spidev%d.%d\n", bus_numb, bus_ce);
-    printf("CE Pin\t\t= Custom GPIO%d\n", ce_pin);
-    #endif
-    printf_P(PSTR("SPI Speedz\t= %d Mhz\n"), static_cast<uint8_t>(spi_speed / 1000000)); //Print the SPI speed on non-Linux devices
-    #if defined(RF24_LINUX)
-    printf("================ NRF Configuration ================\n");
-    #endif // defined(RF24_LINUX)
-
     print_status(get_status());
 
     print_address_register(PSTR("RX_ADDR_P0-1"), RX_ADDR_P0, 2);
@@ -748,19 +327,6 @@ void RF24::printDetails(void)
 
 void RF24::printPrettyDetails(void)
 {
-
-    #if defined(RF24_LINUX)
-    printf("================ SPI Configuration ================\n");
-    uint8_t bus_ce = static_cast<uint8_t>(csn_pin % 10);
-    uint8_t bus_numb = static_cast<uint8_t>((csn_pin - bus_ce) / 10);
-    printf("CSN Pin\t\t\t= /dev/spidev%d.%d\n", bus_numb, bus_ce);
-    printf("CE Pin\t\t\t= Custom GPIO%d\n", ce_pin);
-    #endif
-    printf_P(PSTR("SPI Frequency\t\t= %d Mhz\n"), static_cast<uint8_t>(spi_speed / 1000000)); //Print the SPI speed on non-Linux devices
-    #if defined(RF24_LINUX)
-    printf("================ NRF Configuration ================\n");
-    #endif // defined(RF24_LINUX)
-
     uint8_t channel = getChannel();
     uint16_t frequency = static_cast<uint16_t>(channel + 2400);
     printf_P(PSTR("Channel\t\t\t= %u (~ %u MHz)\r\n"), channel, frequency);
@@ -846,11 +412,6 @@ void RF24::printPrettyDetails(void)
 uint16_t RF24::sprintfPrettyDetails(char* debugging_information)
 {
     const char* format_string = PSTR(
-        "================ SPI Configuration ================\n"
-        "CSN Pin\t\t\t= %d\n"
-        "CE Pin\t\t\t= %d\n"
-        "SPI Frequency\t\t= %d Mhz\n"
-        "================ NRF Configuration ================\n"
         "Channel\t\t\t= %u (~ %u MHz)\n"
         "RF Data Rate\t\t" PRIPSTR "\n"
         "RF Power Amplifier\t" PRIPSTR "\n"
@@ -870,8 +431,7 @@ uint16_t RF24::sprintfPrettyDetails(char* debugging_information)
     const char* format_str3 = PSTR("\nPipe %d (" PRIPSTR ") bound\t= 0x");
 
     uint16_t offset = sprintf_P(
-        debugging_information, format_string, csn_pin, ce_pin,
-        static_cast<uint8_t>(spi_speed / 1000000), getChannel(),
+        debugging_information, format_string, getChannel(),
         static_cast<uint16_t>(getChannel() + 2400),
         (char*)(pgm_read_ptr(&rf24_datarate_e_str_P[getDataRate()])),
         (char*)(pgm_read_ptr(&rf24_pa_dbm_e_str_P[getPALevel()])),
@@ -940,87 +500,26 @@ void RF24::encodeRadioDetails(uint8_t* encoded_details)
             *encoded_details++ = read_register(i);
         }
     }
-    *encoded_details++ = ce_pin >> 4;
-    *encoded_details++ = ce_pin & 0xFF;
-    *encoded_details++ = csn_pin >> 4;
-    *encoded_details++ = csn_pin & 0xFF;
-    *encoded_details = static_cast<uint8_t>((spi_speed / 1000000) | _BV(_is_p_variant * 4));
 }
 #endif // !defined(MINIMAL)
 
 /****************************************************************************/
-#if defined(RF24_SPI_PTR) || defined(DOXYGEN_FORCED)
-// does not apply to RF24_LINUX
 
-bool RF24::begin(_SPI* spiBus)
+bool RF24::begin(void)
 {
-    _spi = spiBus;
     return _init_pins() && _init_radio();
 }
 
 /****************************************************************************/
 
-bool RF24::begin(_SPI* spiBus, rf24_gpio_pin_t _cepin, rf24_gpio_pin_t _cspin)
+bool RF24::begin(RF24_hal* _hal)
 {
-    ce_pin = _cepin;
-    csn_pin = _cspin;
-    return begin(spiBus);
-}
-
-#endif // defined (RF24_SPI_PTR) || defined (DOXYGEN_FORCED)
-
-/****************************************************************************/
-
-bool RF24::begin(rf24_gpio_pin_t _cepin, rf24_gpio_pin_t _cspin)
-{
-    ce_pin = _cepin;
-    csn_pin = _cspin;
+    hal = _hal;
     return begin();
 }
 
 /****************************************************************************/
 
-bool RF24::begin(void)
-{
-#if defined(RF24_LINUX)
-    #if defined(RF24_RPi)
-    switch (csn_pin) { // Ensure valid hardware CS pin
-        case 0: break;
-        case 1: break;
-        // Allow BCM2835 enums for RPi
-        case 8: csn_pin = 0; break;
-        case 7: csn_pin = 1; break;
-        case 18: csn_pin = 10; break; // to make it work on SPI1
-        case 17: csn_pin = 11; break;
-        case 16: csn_pin = 12; break;
-        default: csn_pin = 0; break;
-    }
-    #endif // RF24_RPi
-
-    _SPI.begin(csn_pin, spi_speed);
-
-#elif defined(XMEGA_D3)
-    _spi->begin(csn_pin);
-
-#elif defined(RF24_RP2)
-    _spi = new SPI();
-    _spi->begin(PICO_DEFAULT_SPI ? spi1 : spi0);
-
-#else // using an Arduino platform || defined (LITTLEWIRE)
-
-    #if defined(RF24_SPI_PTR)
-    _spi->begin();
-    #else  // !defined(RF24_SPI_PTR)
-    _SPI.begin();
-    #endif // !defined(RF24_SPI_PTR)
-
-#endif // !defined(XMEGA_D3) && !defined(RF24_LINUX)
-
-    return _init_pins() && _init_radio();
-}
-
-/****************************************************************************/
-
 bool RF24::_init_pins()
 {
     if (!isValid()) {
@@ -1028,46 +527,7 @@ bool RF24::_init_pins()
         return false;
     }
 
-#if defined(RF24_LINUX)
-
-    #if defined(MRAA)
-    GPIO();
-    gpio.begin(ce_pin, csn_pin);
-    #endif
-
-    pinMode(ce_pin, OUTPUT);
-    ce(LOW);
-    delay(100);
-
-#elif defined(LITTLEWIRE)
-    pinMode(csn_pin, OUTPUT);
-    csn(HIGH);
-
-#elif defined(XMEGA_D3)
-    if (ce_pin != csn_pin) {
-        pinMode(ce_pin, OUTPUT);
-    };
-    ce(LOW);
-    csn(HIGH);
-    delay(200);
-
-#else // using an Arduino platform
-
-    // Initialize pins
-    if (ce_pin != csn_pin) {
-        pinMode(ce_pin, OUTPUT);
-        pinMode(csn_pin, OUTPUT);
-    }
-
-    ce(LOW);
-    csn(HIGH);
-
-    #if defined(__ARDUINO_X86__)
-    delay(100);
-    #endif
-#endif // !defined(XMEGA_D3) && !defined(LITTLEWIRE) && !defined(RF24_LINUX)
-
-    return true; // assuming pins are connected properly
+    return hal->begin();
 }
 
 /****************************************************************************/
@@ -1151,7 +611,7 @@ bool RF24::isChipConnected()
 
 bool RF24::isValid()
 {
-    return ce_pin != RF24_PIN_INVALID && csn_pin != RF24_PIN_INVALID;
+    return hal != nullptr;
 }
 
 /****************************************************************************/
@@ -1675,15 +1135,8 @@ void RF24::closeReadingPipe(uint8_t pipe)
 
 void RF24::toggle_features(void)
 {
-    beginTransaction();
-#if defined(RF24_SPI_PTR)
-    status = _spi->transfer(ACTIVATE);
-    _spi->transfer(0x73);
-#else
-    status = _SPI.transfer(ACTIVATE);
-    _SPI.transfer(0x73);
-#endif
-    endTransaction();
+    uint8_t value = 0x73;
+    status = hal->write(ACTIVATE, &value, sizeof(value));
 }
 
 /****************************************************************************/
@@ -1871,6 +1324,11 @@ uint8_t RF24::getARC(void)
     return read_register(OBSERVE_TX) & 0x0F;
 }
 
+uint8_t RF24::getPLOS(void)
+{
+    return (read_register(OBSERVE_TX) >> 4) & 0x0F;
+}
+
 /****************************************************************************/
 
 bool RF24::setDataRate(rf24_datarate_e speed)
diff --git a/RF24.h b/RF24.h
index dbd32ae..74ae35d 100644
--- a/RF24.h
+++ b/RF24.h
@@ -16,12 +16,7 @@
 #define __RF24_H__
 
 #include "RF24_config.h"
-
-#if defined(RF24_LINUX) || defined(LITTLEWIRE)
-    #include "utility/includes.h"
-#elif defined SOFTSPI
-    #include <DigitalIO.h>
-#endif
+#include "RF24_hal.h"
 
 /**
  * @defgroup PALevel Power Amplifier level
@@ -115,29 +110,8 @@ typedef enum
 class RF24
 {
 private:
-#ifdef SOFTSPI
-    SoftSPI<SOFT_SPI_MISO_PIN, SOFT_SPI_MOSI_PIN, SOFT_SPI_SCK_PIN, SPI_MODE> spi;
-#elif defined(SPI_UART)
-    SPIUARTClass uspi;
-#endif
-
-#if defined(RF24_LINUX) || defined(XMEGA_D3) /* XMEGA can use SPI class */
-    SPI spi;
-#endif // defined (RF24_LINUX) || defined (XMEGA_D3)
-#if defined(RF24_SPI_PTR)
-    _SPI* _spi;
-#endif // defined (RF24_SPI_PTR)
-#if defined(MRAA)
-    GPIO gpio;
-#endif
+    RF24_hal *hal;
 
-    rf24_gpio_pin_t ce_pin;  /* "Chip Enable" pin, activates the RX or TX role */
-    rf24_gpio_pin_t csn_pin; /* SPI Chip select */
-    uint32_t spi_speed;      /* SPI Bus Speed */
-#if defined(RF24_LINUX) || defined(XMEGA_D3) || defined(RF24_RP2)
-    uint8_t spi_rxbuff[32 + 1]; //SPI receive buffer (payload max 32 bytes)
-    uint8_t spi_txbuff[32 + 1]; //SPI transmit buffer (payload max 32 bytes + 1 byte for the command)
-#endif
     uint8_t status;                   /* The status byte returned from every SPI transaction */
     uint8_t payload_size;             /* Fixed size of payloads */
     uint8_t pipe0_reading_address[5]; /* Last address set on pipe 0 for reading. */
@@ -146,16 +120,6 @@ private:
     bool _is_p0_rx;                   /* For keeping track of pipe 0's usage in user-triggered RX mode. */
 
 protected:
-    /**
-     * SPI transactions
-     *
-     * Common code for SPI transactions including CSN toggle
-     *
-     */
-    inline void beginTransaction();
-
-    inline void endTransaction();
-
     /** Whether ack payloads are enabled. */
     bool ack_payloads_enabled;
     /** The address width to use (3, 4 or 5 bytes). */
@@ -198,30 +162,15 @@ public:
      *
      * See [Related Pages](pages.html) for device specific information
      *
-     * @param _cepin The pin attached to Chip Enable on the RF module
-     * @param _cspin The pin attached to Chip Select (often labeled CSN) on the radio module.
-     * - For the Arduino Due board, the [Arduino Due extended SPI feature](https://www.arduino.cc/en/Reference/DueExtendedSPI)
-     * is not supported. This means that the Due's pins 4, 10, or 52 are not mandated options (can use any digital output pin) for
-     * the radio's CSN pin.
-     * @param _spi_speed The SPI speed in Hz ie: 1000000 == 1Mhz
-     * - Users can specify default SPI speed by modifying @ref RF24_SPI_SPEED in @ref RF24_config.h
-     *     - For Arduino, the default SPI speed will only be properly configured this way on devices supporting SPI TRANSACTIONS
-     *     - Older/Unsupported Arduino devices will use a default clock divider & settings configuration
-     *     - For Linux: The old way of setting SPI speeds using BCM2835 driver enums has been removed as of v1.3.7
+     * @param _hal A pointer to the device specific hardware abstraction layer
      */
-    RF24(rf24_gpio_pin_t _cepin, rf24_gpio_pin_t _cspin, uint32_t _spi_speed = RF24_SPI_SPEED);
+    RF24(RF24_hal *_hal);
 
     /**
      * A constructor for initializing the radio's hardware dynamically
-     * @warning You MUST use begin(rf24_gpio_pin_t, rf24_gpio_pin_t) or begin(_SPI*, rf24_gpio_pin_t, rf24_gpio_pin_t) to pass both the
-     * digital output pin numbers connected to the radio's CE and CSN pins.
-     * @param _spi_speed The SPI speed in Hz ie: 1000000 == 1Mhz
-     * - Users can specify default SPI speed by modifying @ref RF24_SPI_SPEED in @ref RF24_config.h
-     *     - For Arduino, the default SPI speed will only be properly configured this way on devices supporting SPI TRANSACTIONS
-     *     - Older/Unsupported Arduino devices will use a default clock divider & settings configuration
-     *     - For Linux: The old way of setting SPI speeds using BCM2835 driver enums has been removed as of v1.3.7
+     * @warning You MUST use begin(RF24_hal*)
      */
-    RF24(uint32_t _spi_speed = RF24_SPI_SPEED);
+    RF24(void);
 
 #if defined(RF24_LINUX)
     virtual ~RF24() {};
@@ -243,58 +192,16 @@ public:
      */
     bool begin(void);
 
-#if defined(RF24_SPI_PTR) || defined(DOXYGEN_FORCED)
     /**
      * Same as begin(), but allows specifying a non-default SPI bus to use.
      *
-     * @note This function assumes the `SPI::begin()` method was called before to
-     * calling this function.
-     *
-     * @warning This function is for the Arduino platforms only
-     *
-     * @param spiBus A pointer or reference to an instantiated SPI bus object.
-     * The `_SPI` datatype is a "wrapped" definition that will represent
-     * various SPI implementations based on the specified platform.
-     * @see Review the [Arduino support page](md_docs_arduino.html).
-     *
-     * @return same result as begin()
-     */
-    bool begin(_SPI* spiBus);
-
-    /**
-     * Same as begin(), but allows dynamically specifying a SPI bus, CE pin,
-     * and CSN pin to use.
-     *
-     * @note This function assumes the `SPI::begin()` method was called before to
-     * calling this function.
-     *
      * @warning This function is for the Arduino platforms only
      *
-     * @param spiBus A pointer or reference to an instantiated SPI bus object.
-     * The `_SPI` datatype is a "wrapped" definition that will represent
-     * various SPI implementations based on the specified platform.
-     * @param _cepin The pin attached to Chip Enable on the RF module
-     * @param _cspin The pin attached to Chip Select (often labeled CSN) on the radio module.
-     * - For the Arduino Due board, the [Arduino Due extended SPI feature](https://www.arduino.cc/en/Reference/DueExtendedSPI)
-     * is not supported. This means that the Due's pins 4, 10, or 52 are not mandated options (can use any digital output pin) for the radio's CSN pin.
+     * @param _hal A pointer to the device specific hardware abstraction layer
      *
-     * @see Review the [Arduino support page](md_docs_arduino.html).
-     *
-     * @return same result as begin()
-     */
-    bool begin(_SPI* spiBus, rf24_gpio_pin_t _cepin, rf24_gpio_pin_t _cspin);
-#endif // defined (RF24_SPI_PTR) || defined (DOXYGEN_FORCED)
-
-    /**
-     * Same as begin(), but allows dynamically specifying a CE pin
-     * and CSN pin to use.
-     * @param _cepin The pin attached to Chip Enable on the RF module
-     * @param _cspin The pin attached to Chip Select (often labeled CSN) on the radio module.
-     * - For the Arduino Due board, the [Arduino Due extended SPI feature](https://www.arduino.cc/en/Reference/DueExtendedSPI)
-     * is not supported. This means that the Due's pins 4, 10, or 52 are not mandated options (can use any digital output pin) for the radio's CSN pin.
      * @return same result as begin()
      */
-    bool begin(rf24_gpio_pin_t _cepin, rf24_gpio_pin_t _cspin);
+    bool begin(RF24_hal* _hal);
 
     /**
      * Checks if the chip is connected to the SPI bus
@@ -667,12 +574,12 @@ public:
      * This function uses much less ram than other `*print*Details()` methods.
      *
      * @code
-     * uint8_t encoded_details[43] = {0};
+     * uint8_t encoded_details[38] = {0};
      * radio.encodeRadioDetails(encoded_details);
      * @endcode
      *
      * @param encoded_status The uint8_t array that RF24 radio details are
-     * encoded into. This array must be at least 43 bytes in length; any less would surely
+     * encoded into. This array must be at least 38 bytes in length; any less would surely
      * cause undefined behavior.
      *
      * Registers names and/or data corresponding to the index of the `encoded_details` array:
@@ -704,9 +611,6 @@ public:
      * | 35 |    FIFO_STATUS |
      * | 36 |    DYNPD |
      * | 37 |    FEATURE |
-     * | 38-39 | ce_pin |
-     * | 40-41 | csn_pin |
-     * | 42 |    SPI speed (in MHz) or'd with (isPlusVariant << 4) |
      */
     void encodeRadioDetails(uint8_t* encoded_status);
 
@@ -1644,6 +1548,7 @@ public:
      * @return Returns values from 0 to 15.
      */
     uint8_t getARC(void);
+    uint8_t getPLOS(void);
 
     /**
      * Set the transmission @ref Datarate
@@ -1896,18 +1801,6 @@ private:
      */
     bool _init_pins();
 
-    /**
-     * Set chip select pin
-     *
-     * Running SPI bus at PI_CLOCK_DIV2 so we don't waste time transferring data
-     * and best of all, we make use of the radio's FIFO buffers. A lower speed
-     * means we're less likely to effectively leverage our FIFOs and pay a higher
-     * AVR runtime cost as toll.
-     *
-     * @param mode HIGH to take this unit off the SPI bus, LOW to put it on
-     */
-    void csn(bool mode);
-
     /**
      * Set chip enable
      *
diff --git a/RF24_hal.cpp b/RF24_hal.cpp
new file mode 100644
index 0000000..3cc78e4
--- /dev/null
+++ b/RF24_hal.cpp
@@ -0,0 +1 @@
+#include "RF24_hal.h"
diff --git a/RF24_hal.h b/RF24_hal.h
new file mode 100644
index 0000000..baceab3
--- /dev/null
+++ b/RF24_hal.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "RF24_config.h"
+
+class RF24_hal
+{
+public:
+    virtual void ce(bool level) = 0;
+    virtual uint8_t read(uint8_t cmd, uint8_t* buf, uint8_t len) = 0;
+    virtual uint8_t read(uint8_t cmd, uint8_t* buf, uint8_t data_len, uint8_t blank_len) = 0;
+    virtual uint8_t write(uint8_t cmd, const uint8_t* buf, uint8_t len) = 0;
+    virtual uint8_t write(uint8_t cmd, const uint8_t* buf, uint8_t len, uint8_t blank_len) = 0;
+    virtual bool begin() = 0;
+    virtual void end() = 0;
+};