//··································································································
// A CAN driver for MCP2515
// by Pierre Molinaro
// https://github.com/pierremolinaro/acan2515
//··································································································

#include "ACAN2515.h"

//··································································································
//   MCP2515 COMMANDS
//··································································································

static const uint8_t RESET_COMMAND = 0xC0 ;
static const uint8_t WRITE_COMMAND = 0x02 ;
static const uint8_t READ_COMMAND  = 0x03 ;
static const uint8_t BIT_MODIFY_COMMAND         = 0x05 ;
static const uint8_t LOAD_TX_BUFFER_COMMAND     = 0x40 ;
static const uint8_t REQUEST_TO_SEND_COMMAND    = 0x80 ;
static const uint8_t READ_FROM_RXB0SIDH_COMMAND = 0x90 ;
static const uint8_t READ_FROM_RXB1SIDH_COMMAND = 0x94 ;
static const uint8_t READ_STATUS_COMMAND        = 0xA0 ;
static const uint8_t RX_STATUS_COMMAND          = 0xB0 ;

//··································································································
//   MCP2515 REGISTERS
//··································································································

static const uint8_t BFPCTRL_REGISTER   = 0x0C ;
static const uint8_t TXRTSCTRL_REGISTER = 0x0D ;
static const uint8_t CANSTAT_REGISTER   = 0x0E ;
static const uint8_t CANCTRL_REGISTER   = 0x0F ;
static const uint8_t TEC_REGISTER       = 0x1C ;
static const uint8_t REC_REGISTER       = 0x1D ;
static const uint8_t RXM0SIDH_REGISTER  = 0x20 ;
static const uint8_t RXM1SIDH_REGISTER  = 0x24 ;
static const uint8_t CNF3_REGISTER      = 0x28 ;
static const uint8_t CNF2_REGISTER      = 0x29 ;
static const uint8_t CNF1_REGISTER      = 0x2A ;
static const uint8_t CANINTF_REGISTER   = 0x2C ;
static const uint8_t EFLG_REGISTER      = 0x2D ;
static const uint8_t TXB0CTRL_REGISTER  = 0x30 ;
static const uint8_t TXB1CTRL_REGISTER  = 0x40 ;
static const uint8_t TXB2CTRL_REGISTER  = 0x50 ;
static const uint8_t RXB0CTRL_REGISTER  = 0x60 ;
static const uint8_t RXB1CTRL_REGISTER  = 0x70 ;

static const uint8_t RXFSIDH_REGISTER [6] = {0x00, 0x04, 0x08, 0x10, 0x14, 0x18} ;

//··································································································
// Note about ESP32
//··································································································
//
// It appears that Arduino ESP32 interrupts are managed in a completely different way
// from "usual" Arduino:
//   - SPI.usingInterrupt is not implemented;
//   - noInterrupts() and interrupts() are NOPs;
//   - interrupt service routines should be fast, otherwise you get smothing like
//     "Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1)".

// So we handle the ESP32 interrupt in the following way:
//   - interrupt service routine performs a xSemaphoreGive on mISRSemaphore of can driver
//   - this activates the myESP32Task task that performs "isr_core" that is done
//     by interrupt service routine in "usual" Arduino;
//   - as this task runs in parallel with setup / loop routines, SPI access is natively
//     protected by the beginTransaction / endTransaction pair, that manages a mutex.

//··································································································

#ifdef ARDUINO_ARCH_ESP32
  static void myESP32Task (void * pData) {
    ACAN2515 * canDriver = (ACAN2515 *) pData ;
    while (1) {
      canDriver->attachMCP2515InterruptPin () ;
      xSemaphoreTake (canDriver->mISRSemaphore, portMAX_DELAY) ;
      bool loop = true ;
      while (loop) {
        loop = canDriver->isr_core () ;
      }
    }
  }
#endif

//··································································································

#ifdef ARDUINO_ARCH_ESP32
  void ACAN2515::attachMCP2515InterruptPin (void) {
    attachInterrupt (digitalPinToInterrupt (mINT), mInterruptServiceRoutine, ONLOW) ;
  }
#endif

//··································································································
//   CONSTRUCTOR, HARDWARE SPI
//··································································································

ACAN2515::ACAN2515 (const uint8_t inCS,  // CS input of MCP2515
                    SPIClass & inSPI, // Hardware SPI object
                    const uint8_t inINT) : // INT output of MCP2515
mSPI (inSPI),
mSPISettings (10UL * 1000UL * 1000UL, MSBFIRST, SPI_MODE0),  // 10 MHz, UL suffix is required for Arduino Uno
mCS (inCS),
mINT (inINT),
mRolloverEnable (false),
#ifdef ARDUINO_ARCH_ESP32
  mISRSemaphore (xSemaphoreCreateCounting (10, 0)),
#endif
mReceiveBuffer (),
mCallBackFunctionArray (),
mTXBIsFree () {
  for (uint8_t i=0 ; i<6 ; i++) {
    mCallBackFunctionArray [i] = NULL ;
  }
}

//··································································································
//   BEGIN
//··································································································

uint16_t ACAN2515::begin (const ACAN2515Settings & inSettings,
                          void (* inInterruptServiceRoutine) (void)) {

  return beginWithoutFilterCheck (inSettings, inInterruptServiceRoutine, ACAN2515Mask (), ACAN2515Mask (), NULL, 0) ;
}

//··································································································

uint16_t ACAN2515::begin (const ACAN2515Settings & inSettings,
                          void (* inInterruptServiceRoutine) (void),
                          const ACAN2515Mask inRXM0,
                          const ACAN2515AcceptanceFilter inAcceptanceFilters [],
                          const uint8_t inAcceptanceFilterCount) {
  uint16_t errorCode = 0 ;
  if (inAcceptanceFilterCount == 0) {
    errorCode = kOneFilterMaskRequiresOneOrTwoAcceptanceFilters ;
  }else if (inAcceptanceFilterCount > 2) {
    errorCode = kOneFilterMaskRequiresOneOrTwoAcceptanceFilters ;
  }else if (inAcceptanceFilters == NULL) {
    errorCode = kAcceptanceFilterArrayIsNULL ;
  }else{
    errorCode = beginWithoutFilterCheck (inSettings, inInterruptServiceRoutine,
                                         inRXM0, inRXM0, inAcceptanceFilters, inAcceptanceFilterCount) ;
  }
  return errorCode ;
}

//··································································································

uint16_t ACAN2515::begin (const ACAN2515Settings & inSettings,
                          void (* inInterruptServiceRoutine) (void),
                          const ACAN2515Mask inRXM0,
                          const ACAN2515Mask inRXM1,
                          const ACAN2515AcceptanceFilter inAcceptanceFilters [],
                          const uint8_t inAcceptanceFilterCount) {
  uint16_t errorCode = 0 ;
  if (inAcceptanceFilterCount < 3) {
    errorCode = kTwoFilterMasksRequireThreeToSixAcceptanceFilters ;
  }else if (inAcceptanceFilterCount > 6) {
    errorCode = kTwoFilterMasksRequireThreeToSixAcceptanceFilters ;
  }else if (inAcceptanceFilters == NULL) {
    errorCode = kAcceptanceFilterArrayIsNULL ;
  }else{
    errorCode = beginWithoutFilterCheck (inSettings, inInterruptServiceRoutine,
                                         inRXM0, inRXM1, inAcceptanceFilters, inAcceptanceFilterCount) ;
  }
  return errorCode ;
}

//··································································································

uint16_t ACAN2515::beginWithoutFilterCheck (const ACAN2515Settings & inSettings,
                                            void (* inInterruptServiceRoutine) (void),
                                            const ACAN2515Mask inRXM0,
                                            const ACAN2515Mask inRXM1,
                                            const ACAN2515AcceptanceFilter inAcceptanceFilters [],
                                            const uint8_t inAcceptanceFilterCount) {
  uint16_t errorCode = 0 ; // Means no error
//----------------------------------- Check mINT has interrupt capability
  const int8_t itPin = digitalPinToInterrupt (mINT) ;
  if ((mINT != 255) && (itPin == NOT_AN_INTERRUPT)) {
    errorCode = kINTPinIsNotAnInterrupt ;
  }
//----------------------------------- Check interrupt service routine is not null
  if ((mINT != 255) && (inInterruptServiceRoutine == NULL)) {
    errorCode |= kISRIsNull ;
  }
//----------------------------------- Check consistency between ISR and INT pin
  if ((mINT == 255) && (inInterruptServiceRoutine != NULL)) {
    errorCode |= kISRNotNullAndNoIntPin ;
  }
//----------------------------------- if no error, configure port and MCP2515
  if (errorCode == 0) {
  //--- Configure ports
    if (mINT != 255) { // 255 means interrupt is not used
      pinMode (mINT, INPUT_PULLUP) ;
    }
    pinMode (mCS, OUTPUT) ;
    digitalWrite (mCS, HIGH) ;  // CS is high outside a command
  //--- Send software reset to MCP2515
    mSPI.beginTransaction (mSPISettings) ;
      select () ;
        mSPI.transfer (RESET_COMMAND) ;
      unselect () ;
    mSPI.endTransaction () ;
  //--- DS20001801J, page 55: The Oscillator Start-up Timer keeps the device in a Reset
  // state for 128 OSC1 clock cycles after the occurrence of a Power-on Reset, SPI Reset,
  // after the assertion of the RESET pin, and after a wake-up from Sleep mode
  // Fot a 1 MHz clock --> 128 µs
  // So delayMicroseconds (10) is too short --> use delay (2)
  //    delayMicroseconds (10) ; // Removed in release 2.1.2
    delay (2) ; // Added in release 2.1.2
  //--- Internal begin
    errorCode = internalBeginOperation (inSettings,
                                        inRXM0,
                                        inRXM1,
                                        inAcceptanceFilters,
                                        inAcceptanceFilterCount) ;
  }
//--- Configure interrupt only if no error (thanks to mvSarma)
  if (errorCode == 0) {
     if (mINT != 255) { // 255 means interrupt is not used
      #ifdef ARDUINO_ARCH_ESP32
        mInterruptServiceRoutine = inInterruptServiceRoutine ;
      #else
        mSPI.usingInterrupt (itPin) ; // usingInterrupt is not implemented in Arduino ESP32
        attachInterrupt (itPin, inInterruptServiceRoutine, LOW) ;
      #endif
    }
    #ifdef ARDUINO_ARCH_ESP32
      xTaskCreate (myESP32Task, "ACAN2515Handler", 1024, this, 256, NULL) ;
    #endif
  }
//----------------------------------- Return
  return errorCode ;
}

//··································································································
//   MESSAGE RECEPTION
//··································································································

bool ACAN2515::available (void) {
  #ifdef ARDUINO_ARCH_ESP32
    mSPI.beginTransaction (mSPISettings) ; // For ensuring mutual exclusion access
  #else
    noInterrupts () ;
  #endif
    const bool hasReceivedMessage = mReceiveBuffer.count () > 0 ;
  #ifdef ARDUINO_ARCH_ESP32
    mSPI.endTransaction () ;
  #else
    interrupts () ;
  #endif
  return hasReceivedMessage ;
}

//··································································································

bool ACAN2515::receive (CANMessage & outMessage) {
  #ifdef ARDUINO_ARCH_ESP32
    mSPI.beginTransaction (mSPISettings) ; // For ensuring mutual exclusion access
  #else
    noInterrupts () ;
  #endif
    const bool hasReceivedMessage = mReceiveBuffer.remove (outMessage) ;
  #ifdef ARDUINO_ARCH_ESP32
    mSPI.endTransaction () ;
  #else
    interrupts () ;
  #endif
//---
  return hasReceivedMessage ;
}

//··································································································

bool ACAN2515::dispatchReceivedMessage (const tFilterMatchCallBack inFilterMatchCallBack) {
  CANMessage receivedMessage ;
  const bool hasReceived = receive (receivedMessage) ;
  if (hasReceived) {
    const uint8_t filterIndex = receivedMessage.idx ;
    if (NULL != inFilterMatchCallBack) {
      inFilterMatchCallBack (filterIndex) ;
    }
    ACANCallBackRoutine callBackFunction = mCallBackFunctionArray [filterIndex] ;
    if (NULL != callBackFunction) {
      callBackFunction (receivedMessage) ;
    }
  }
  return hasReceived ;
}

//··································································································
//  INTERRUPTS ARE DISABLED WHEN THESE FUNCTIONS ARE EXECUTED
//··································································································

uint16_t ACAN2515::internalBeginOperation (const ACAN2515Settings & inSettings,
                                           const ACAN2515Mask inRXM0,
                                           const ACAN2515Mask inRXM1,
                                           const ACAN2515AcceptanceFilter inAcceptanceFilters [],
                                           const uint8_t inAcceptanceFilterCount) {
  uint16_t errorCode = 0 ; // Ok be default
//----------------------------------- Check if MCP2515 is accessible
  mSPI.beginTransaction (mSPISettings) ;
    write2515Register (CNF1_REGISTER, 0x55) ;
    bool ok = read2515Register (CNF1_REGISTER) == 0x55 ;
    if (ok) {
      write2515Register (CNF1_REGISTER, 0xAA) ;
      ok = read2515Register (CNF1_REGISTER) == 0xAA ;
    }
    if (!ok) {
      errorCode = kNoMCP2515 ;
    }
  mSPI.endTransaction () ;
//----------------------------------- Check if settings are correct
  if (!inSettings.mBitRateClosedToDesiredRate) {
    errorCode |= kTooFarFromDesiredBitRate ;
  }
  if (inSettings.CANBitSettingConsistency () != 0) {
    errorCode |= kInconsistentBitRateSettings ;
  }
//----------------------------------- Allocate buffer
  if (!mReceiveBuffer.initWithSize (inSettings.mReceiveBufferSize)) {
    errorCode |= kCannotAllocateReceiveBuffer ;
  }
  if (!mTransmitBuffer [0].initWithSize (inSettings.mTransmitBuffer0Size)) {
    errorCode |= kCannotAllocateTransmitBuffer0 ;
  }
  if (!mTransmitBuffer [1].initWithSize (inSettings.mTransmitBuffer1Size)) {
    errorCode |= kCannotAllocateTransmitBuffer1 ;
  }
  if (!mTransmitBuffer [2].initWithSize (inSettings.mTransmitBuffer2Size)) {
    errorCode |= kCannotAllocateTransmitBuffer2 ;
  }
  mTXBIsFree [0] = true ;
  mTXBIsFree [1] = true ;
  mTXBIsFree [2] = true ;
//----------------------------------- If ok, perform configuration
  if (errorCode == 0) {
    mSPI.beginTransaction (mSPISettings) ;
  //----------------------------------- Set CNF3, CNF2, CNF1 and CANINTE registers
    select () ;
    mSPI.transfer (WRITE_COMMAND) ;
    mSPI.transfer (CNF3_REGISTER) ;
  //--- Register CNF3:
  //  Bit 7: SOF
  //  bit 6 --> 0: No Wake-up Filter bit
  //  Bit 5-3: -
  //  Bit 2-0: PHSEG2 - 1
    const uint8_t cnf3 =
      ((inSettings.mCLKOUT_SOF_pin == ACAN2515Settings::SOF) << 6) /* SOF */ |
      ((inSettings.mPhaseSegment2 - 1) << 0) /* PHSEG2 */
    ;
   mSPI.transfer (cnf3) ;
  //--- Register CNF2:
  //  Bit 7 --> 1: BLTMODE
  //  bit 6: SAM
  //  Bit 5-3: PHSEG1 - 1
  //  Bit 2-0: PRSEG - 1
    const uint8_t cnf2 =
      0x80 /* BLTMODE */ |
      (inSettings.mTripleSampling << 6) /* SAM */ |
      ((inSettings.mPhaseSegment1 - 1) << 3) /* PHSEG1 */ |
      ((inSettings.mPropagationSegment - 1) << 0) /* PRSEG */
    ;
    mSPI.transfer (cnf2) ;
  //--- Register CNF1:
  //  Bit 7-6: SJW - 1
  //  Bit 5-0: BRP - 1
    const uint8_t cnf1 =
      ((inSettings.mSJW - 1) << 6) /* SJW */ | // Incorrect SJW setting fixed in 2.0.1
      ((inSettings.mBitRatePrescaler - 1) << 0) /* BRP */
    ;
    mSPI.transfer (cnf1) ;
  //--- Register CANINTE: activate interrupts
  //  Bit 7 --> 0: MERRE
  //  Bit 6 --> 0: WAKIE
  //  Bit 5 --> 0: ERRIE
  //  Bit 4 --> 1: TX2IE
  //  Bit 3 --> 1: TX1IE
  //  Bit 2 --> 1: TX0IE
  //  Bit 1 --> 1: RX1IE
  //  Bit 0 --> 1: RX0IE
    mSPI.transfer (0x1F) ;
    unselect () ;
  //----------------------------------- Deactivate the RXnBF Pins (High Impedance State)
    write2515Register (BFPCTRL_REGISTER, 0) ;
  //----------------------------------- Set TXnRTS as inputs
    write2515Register (TXRTSCTRL_REGISTER, 0);
  //----------------------------------- RXBnCTRL
    mRolloverEnable = inSettings.mRolloverEnable ;
    const uint8_t acceptAll = (inAcceptanceFilterCount == 0) ? 0x60 : 0x00 ;
    write2515Register (RXB0CTRL_REGISTER, acceptAll | (uint8_t (inSettings.mRolloverEnable) << 2)) ;
    write2515Register (RXB1CTRL_REGISTER, acceptAll) ;
  //----------------------------------- Setup mask registers
    setupMaskRegister (inRXM0, RXM0SIDH_REGISTER) ;
    setupMaskRegister (inRXM1, RXM1SIDH_REGISTER) ;
    if (inAcceptanceFilterCount > 0) {
      uint8_t idx = 0 ;
      while (idx < inAcceptanceFilterCount) {
        setupMaskRegister (inAcceptanceFilters [idx].mMask, RXFSIDH_REGISTER [idx]) ;
        mCallBackFunctionArray [idx] = inAcceptanceFilters [idx].mCallBack ;
        idx += 1 ;
      }
      while (idx < 6) {
        setupMaskRegister (inAcceptanceFilters [inAcceptanceFilterCount-1].mMask, RXFSIDH_REGISTER [idx]) ;
        mCallBackFunctionArray [idx] = inAcceptanceFilters [inAcceptanceFilterCount-1].mCallBack ;
        idx += 1 ;
      }
    }
  //----------------------------------- Set TXBi priorities
    write2515Register (TXB0CTRL_REGISTER, inSettings.mTXBPriority & 3) ;
    write2515Register (TXB1CTRL_REGISTER, (inSettings.mTXBPriority >> 2) & 3) ;
    write2515Register (TXB2CTRL_REGISTER, (inSettings.mTXBPriority >> 4) & 3) ;
    mSPI.endTransaction () ;
  //----------------------------------- Reset device to requested mode
    uint8_t canctrl = inSettings.mOneShotModeEnabled ? (1 << 3) : 0 ;
    switch (inSettings.mCLKOUT_SOF_pin) {
    case ACAN2515Settings::CLOCK :
      canctrl |= 0x04 | 0x00 ; // Same as default setting
      break ;
    case ACAN2515Settings::CLOCK2 :
      canctrl |= 0x04 | 0x01 ;
      break ;
    case ACAN2515Settings::CLOCK4 :
      canctrl |= 0x04 | 0x02 ;
      break ;
    case ACAN2515Settings::CLOCK8 :
      canctrl |= 0x04 | 0x03 ;
      break ;
    case ACAN2515Settings::SOF :
      canctrl |= 0x04 ;
      break ;
    case ACAN2515Settings::HiZ :
      break ;
    }
  //--- Request mode
    const uint8_t requestedMode = (uint8_t) inSettings.mRequestedMode ;
    errorCode |= setRequestedMode (canctrl | requestedMode) ;
  }
//-----------------------------------
  return errorCode ;
}

//··································································································
//   setRequestedMode (private method)
//··································································································

uint16_t ACAN2515::setRequestedMode (const uint8_t inCANControlRegister) {
  uint16_t errorCode = 0 ;
//--- Request mode
  mSPI.beginTransaction (mSPISettings) ;
    write2515Register (CANCTRL_REGISTER, inCANControlRegister) ;
  mSPI.endTransaction () ;
//--- Wait until requested mode is reached (during 1 or 2 ms)
  bool wait = true ;
  const uint32_t deadline = millis () + 2 ;
  while (wait) {
    mSPI.beginTransaction (mSPISettings) ;
      const uint8_t actualMode = read2515Register (CANSTAT_REGISTER) & 0xE0 ;
    mSPI.endTransaction () ;
    wait = actualMode != (inCANControlRegister & 0xE0) ;
    if (wait && (millis () >= deadline)) {
      errorCode |= kRequestedModeTimeOut ;
      wait = false ;
    }
  }
//---
  return errorCode ;
}

//··································································································
//    Change Mode
//··································································································

uint16_t ACAN2515::changeModeOnTheFly (const ACAN2515Settings::RequestedMode inRequestedMode) {
//--- Read current mode register (for saving settings of bits 0 ... 4)
  mSPI.beginTransaction (mSPISettings) ;
    const uint8_t currentMode = read2515Register (CANCTRL_REGISTER) ;
  mSPI.endTransaction () ;
//--- New mode
  const uint8_t newMode = (currentMode & 0x1F) | (uint8_t) inRequestedMode ;
//--- Set new mode
  const uint16_t errorCode = setRequestedMode (newMode) ;
//---
  return errorCode ;
}

//··································································································
//    Set filters on the fly
//··································································································

uint16_t ACAN2515::setFiltersOnTheFly (void) {
  return internalSetFiltersOnTheFly (ACAN2515Mask (), ACAN2515Mask (), NULL, 0) ;
}

//··································································································

uint16_t ACAN2515::setFiltersOnTheFly (const ACAN2515Mask inRXM0,
                                       const ACAN2515AcceptanceFilter inAcceptanceFilters [],
                                       const uint8_t inAcceptanceFilterCount) {
  uint16_t errorCode = 0 ;
  if (inAcceptanceFilterCount == 0) {
    errorCode = kOneFilterMaskRequiresOneOrTwoAcceptanceFilters ;
  }else if (inAcceptanceFilterCount > 2) {
    errorCode = kOneFilterMaskRequiresOneOrTwoAcceptanceFilters ;
  }else if (inAcceptanceFilters == NULL) {
    errorCode = kAcceptanceFilterArrayIsNULL ;
  }else{
    errorCode = internalSetFiltersOnTheFly (inRXM0, ACAN2515Mask (), inAcceptanceFilters, inAcceptanceFilterCount) ;
  }
  return errorCode ;
}

//··································································································

uint16_t ACAN2515::setFiltersOnTheFly (const ACAN2515Mask inRXM0,
                                       const ACAN2515Mask inRXM1,
                                       const ACAN2515AcceptanceFilter inAcceptanceFilters [],
                                       const uint8_t inAcceptanceFilterCount) {
  uint16_t errorCode = 0 ;
  if (inAcceptanceFilterCount < 3) {
    errorCode = kTwoFilterMasksRequireThreeToSixAcceptanceFilters ;
  }else if (inAcceptanceFilterCount > 6) {
    errorCode = kTwoFilterMasksRequireThreeToSixAcceptanceFilters ;
  }else if (inAcceptanceFilters == NULL) {
    errorCode = kAcceptanceFilterArrayIsNULL ;
  }else{
    errorCode = internalSetFiltersOnTheFly (inRXM0, inRXM1, inAcceptanceFilters, inAcceptanceFilterCount) ;
  }
  return errorCode ;
}

//··································································································

uint16_t ACAN2515::internalSetFiltersOnTheFly (const ACAN2515Mask inRXM0,
                                               const ACAN2515Mask inRXM1,
                                               const ACAN2515AcceptanceFilter inAcceptanceFilters [],
                                               const uint8_t inAcceptanceFilterCount) {
//--- Read current mode register
  mSPI.beginTransaction (mSPISettings) ;
    const uint8_t currentMode = read2515Register (CANCTRL_REGISTER) ;
  mSPI.endTransaction () ;
//--- Request configuration mode
  const uint8_t configurationMode = (currentMode & 0x1F) | (0b100 << 5) ; // Preserve bits 0 ... 4
  uint16_t errorCode = setRequestedMode (configurationMode) ;
//--- Setup mask registers
  if (errorCode == 0) {
    const uint8_t acceptAll = (inAcceptanceFilterCount == 0) ? 0x60 : 0x00 ;
    write2515Register (RXB0CTRL_REGISTER, acceptAll | (uint8_t (mRolloverEnable) << 2)) ;
    write2515Register (RXB1CTRL_REGISTER, acceptAll) ;
    setupMaskRegister (inRXM0, RXM0SIDH_REGISTER) ;
    setupMaskRegister (inRXM1, RXM1SIDH_REGISTER) ;
    if (inAcceptanceFilterCount > 0) {
      uint8_t idx = 0 ;
      while (idx < inAcceptanceFilterCount) {
        setupMaskRegister (inAcceptanceFilters [idx].mMask, RXFSIDH_REGISTER [idx]) ;
        mCallBackFunctionArray [idx] = inAcceptanceFilters [idx].mCallBack ;
        idx += 1 ;
      }
      while (idx < 6) {
        setupMaskRegister (inAcceptanceFilters [inAcceptanceFilterCount-1].mMask, RXFSIDH_REGISTER [idx]) ;
        mCallBackFunctionArray [idx] = inAcceptanceFilters [inAcceptanceFilterCount-1].mCallBack ;
        idx += 1 ;
      }
    }
  }
//--- Restore saved mode
  if (errorCode == 0) {
    errorCode = setRequestedMode (currentMode) ;
  }
//---
  return errorCode ;
}

//··································································································
//    end
//··································································································

void ACAN2515::end (void) {
//--- Remove interrupt capability of mINT pin
  if (mINT != 255) {
    detachInterrupt (digitalPinToInterrupt (mINT)) ;
  }
//--- Request configuration mode
  const uint8_t configurationMode = (0b100 << 5) ;
  const uint16_t errorCode __attribute__((unused)) = setRequestedMode (configurationMode) ;
//--- Deallocate driver buffers
  mTransmitBuffer [0].free () ;
  mTransmitBuffer [1].free () ;
  mTransmitBuffer [2].free () ;
  mReceiveBuffer.free () ;
}


//··································································································
//    POLLING (ESP32)
//··································································································

#ifdef ARDUINO_ARCH_ESP32
  void ACAN2515::poll (void) {
    xSemaphoreGive (mISRSemaphore) ;
  }
#endif

//··································································································
//    POLLING (other than ESP32)
//··································································································

#ifndef ARDUINO_ARCH_ESP32
  void ACAN2515::poll (void) {
    noInterrupts () ;
      while (isr_core ()) {}
    interrupts () ;
  }
#endif

//··································································································
//   INTERRUPT SERVICE ROUTINE (ESP32)
// https://stackoverflow.com/questions/51750377/how-to-disable-interrupt-watchdog-in-esp32-or-increase-isr-time-limit
//··································································································

#ifdef ARDUINO_ARCH_ESP32
  void IRAM_ATTR ACAN2515::isr (void) {
    detachInterrupt (digitalPinToInterrupt (mINT)) ;
    BaseType_t xHigherPriorityTaskWoken = pdFALSE ;
    xSemaphoreGiveFromISR (mISRSemaphore, &xHigherPriorityTaskWoken) ;
    if (xHigherPriorityTaskWoken) {
      portYIELD_FROM_ISR () ;
    }
  }
#endif

//··································································································
//   INTERRUPT SERVICE ROUTINE (other than ESP32)
//··································································································

#ifndef ARDUINO_ARCH_ESP32
  void ACAN2515::isr (void) {
    isr_core () ;
  }
#endif

//··································································································

bool ACAN2515::isr_core (void) {
  bool handled = false ;
  mSPI.beginTransaction (mSPISettings) ;
  uint8_t itStatus = read2515Register (CANSTAT_REGISTER) & 0x0E ;
  while (itStatus != 0) {
    handled = true ;
    switch (itStatus) {
    case 0 : // No interrupt
      break ;
    case 1 << 1 : // Error interrupt
      bitModify2515Register (CANINTF_REGISTER, 0x20, 0) ; // Ack interrupt
      break ;
    case 2 << 1 : // Wake-up interrupt
      bitModify2515Register (CANINTF_REGISTER, 0x40, 0) ; // Ack interrupt
      break ;
    case 3 << 1 : // TXB0 interrupt
      handleTXBInterrupt (0) ;
      break ;
    case 4 << 1 : // TXB1 interrupt
      handleTXBInterrupt (1) ;
      break ;
    case 5 << 1 : // TXB2 interrupt
      handleTXBInterrupt (2) ;
      break ;
    case 6 << 1 : // RXB0 interrupt
    case 7 << 1 : // RXB1 interrupt
      handleRXBInterrupt () ;
      break ;
    }
    itStatus = read2515Register (CANSTAT_REGISTER) & 0x0E ;
  }
  mSPI.endTransaction () ;
  return handled ;
}

//··································································································
// This function is called by ISR when a MCP2515 receive buffer becomes full

void ACAN2515::handleRXBInterrupt (void) {
  const uint8_t rxStatus = read2515RxStatus () ; // Bit 6: message in RXB0, bit 7: message in RXB1
  const bool received = (rxStatus & 0xC0) != 0 ;
  if (received) { // Message in RXB0 and / or RXB1
    const bool accessRXB0 = (rxStatus & 0x40) != 0 ;
    CANMessage message ;
  //--- Set idx field to matching receive filter
    message.idx = rxStatus & 0x07 ;
    if (message.idx > 5) {
      message.idx -= 6 ;
    }
  //---
    select () ;
    mSPI.transfer (accessRXB0 ? READ_FROM_RXB0SIDH_COMMAND : READ_FROM_RXB1SIDH_COMMAND) ;
  //--- SIDH
    message.id = mSPI.transfer (0) ;
    message.id <<= 3 ;
  //--- SIDL
    const uint32_t sidl = mSPI.transfer (0) ;
    message.id |= sidl >> 5 ;
    message.rtr = (sidl & 0x10) != 0 ; // Only significant for standard frame
    message.ext = (sidl & 0x08) != 0 ;
  //--- EID8
    const uint32_t eid8 = mSPI.transfer (0) ;
    if (message.ext) {
      message.id <<= 2 ;
      message.id |= (sidl & 0x03) ;
      message.id <<= 8 ;
      message.id |= eid8 ;
    }
  //--- EID0
    const uint32_t eid0 = mSPI.transfer (0) ;
    if (message.ext) {
      message.id <<= 8 ;
      message.id |= eid0 ;
    }
  //--- DLC
    const uint8_t dlc = mSPI.transfer (0) ;
    message.len = dlc & 0x0F ;
    if (message.ext) { // Added in 2.1.1 (thanks to Achilles)
      message.rtr = (dlc & 0x40) != 0 ; // RTR bit in DLC is significant only for extended frame
    }
  //--- Read data
    for (int i=0 ; i<message.len ; i++) {
      message.data [i] = mSPI.transfer (0) ;
    }
  //---
    unselect () ;
  //--- Free receive buffer command
    bitModify2515Register (CANINTF_REGISTER, accessRXB0 ? 0x01 : 0x02, 0) ;
  //--- Enter received message in receive buffer (if not full)
    mReceiveBuffer.append (message) ;
  }
}

//··································································································
// This function is called by ISR when a MCP2515 transmit buffer becomes empty

void ACAN2515::handleTXBInterrupt (const uint8_t inTXB) { // inTXB value is 0, 1 or 2
//--- Acknowledge interrupt
  bitModify2515Register (CANINTF_REGISTER, 0x04 << inTXB, 0) ;
//--- Send an other message ?
  CANMessage message ;
  const bool ok = mTransmitBuffer [inTXB].remove (message) ;
  if (ok) {
    internalSendMessage (message, inTXB) ;
  }else{
    mTXBIsFree [inTXB] = true ;
  }
}

//··································································································

void ACAN2515::internalSendMessage (const CANMessage & inFrame, const uint8_t inTXB) { // inTXB is 0, 1 or 2
//--- Send command
//      send via TXB0: 0x81
//      send via TXB1: 0x82
//      send via TXB2: 0x84
  const uint8_t sendCommand = REQUEST_TO_SEND_COMMAND | (1 << inTXB) ;
//--- Load TX buffer command
//      Load TXB0, start at TXB0SIDH: 0x40
//      Load TXB1, start at TXB1SIDH: 0x42
//      Load TXB2, start at TXB2SIDH: 0x44
  const uint8_t loadTxBufferCommand = LOAD_TX_BUFFER_COMMAND | (inTXB << 1) ;
//--- Send message
  select () ;
  mSPI.transfer (loadTxBufferCommand) ;
  if (inFrame.ext) { // Extended frame
    uint32_t v = inFrame.id >> 21 ;
    mSPI.transfer ((uint8_t) v) ; // ID28 ... ID21 --> SIDH
    v  = (inFrame.id >> 13) & 0xE0 ; // ID20, ID19, ID18 in bits 7, 6, 5
    v |= (inFrame.id >> 16) & 0x03 ; // ID17, ID16 in bits 1, 0
    v |= 0x08 ; // Extended bit
    mSPI.transfer ((uint8_t) v) ; // ID20, ID19, ID18, -, 1, -, ID17, ID16 --> SIDL
    v  = (inFrame.id >> 8) & 0xFF ; // ID15, ..., ID8
    mSPI.transfer ((uint8_t) v) ; // ID15, ID14, ID13, ID12, ID11, ID10, ID9, ID8 --> EID8
    v  = inFrame.id & 0xFF ; // ID7, ..., ID0
    mSPI.transfer ((uint8_t) v) ; // ID7, ID6, ID5, ID4, ID3, ID2, ID1, ID0 --> EID0
  }else{ // Standard frame
    uint32_t v = inFrame.id >> 3 ;
    mSPI.transfer ((uint8_t) v) ; // ID10 ... ID3 --> SIDH
    v  = (inFrame.id << 5) & 0xE0 ; // ID2, ID1, ID0 in bits 7, 6, 5
    mSPI.transfer ((uint8_t) v) ; // ID2, ID1, ID0, -, 0, -, 0, 0 --> SIDL
    mSPI.transfer (0x00) ; // any value --> EID8
    mSPI.transfer (0x00) ; // any value --> EID0
  }
//--- DLC
  uint8_t v = inFrame.len ;
  if (v > 8) {
    v = 8 ;
  }
  if (inFrame.rtr) {
    v |= 0x40 ;
  }
  mSPI.transfer (v) ;
//--- Send data
  if (!inFrame.rtr) {
    for (uint8_t i=0 ; i<inFrame.len ; i++) {
      mSPI.transfer (inFrame.data [i]) ;
    }
  }
  unselect () ;
//--- Write send command
  select () ;
    mSPI.transfer (sendCommand) ;
  unselect () ;
}

//··································································································
//  INTERNAL SPI FUNCTIONS
//··································································································

void ACAN2515::write2515Register (const uint8_t inRegister, const uint8_t inValue) {
  select () ;
    mSPI.transfer (WRITE_COMMAND) ;
    mSPI.transfer (inRegister) ;
    mSPI.transfer (inValue) ;
  unselect () ;
}

//··································································································

uint8_t ACAN2515::read2515Register (const uint8_t inRegister) {
  select () ;
    mSPI.transfer (READ_COMMAND) ;
    mSPI.transfer (inRegister) ;
    const uint8_t readValue = mSPI.transfer (0) ;
  unselect () ;
  return readValue ;
}

//··································································································

uint8_t ACAN2515::read2515Status (void) {
  select () ;
    mSPI.transfer (READ_STATUS_COMMAND) ;
    const uint8_t readValue = mSPI.transfer (0) ;
  unselect () ;
  return readValue ;
}

//··································································································

uint8_t ACAN2515::read2515RxStatus (void) {
  select () ;
    mSPI.transfer (RX_STATUS_COMMAND) ;
    const uint8_t readValue = mSPI.transfer (0) ;
  unselect () ;
  return readValue ;
}

//··································································································

void ACAN2515::bitModify2515Register (const uint8_t inRegister,
                                      const uint8_t inMask,
                                      const uint8_t inData) {
  select () ;
    mSPI.transfer (BIT_MODIFY_COMMAND) ;
    mSPI.transfer (inRegister) ;
    mSPI.transfer (inMask) ;
    mSPI.transfer (inData) ;
  unselect () ;
}

//··································································································

void ACAN2515::setupMaskRegister (const ACAN2515Mask inMask, const uint8_t inRegister) {
  select () ;
    mSPI.transfer (WRITE_COMMAND) ;
    mSPI.transfer (inRegister) ;
    mSPI.transfer (inMask.mSIDH) ;
    mSPI.transfer (inMask.mSIDL) ;
    mSPI.transfer (inMask.mEID8) ;
    mSPI.transfer (inMask.mEID0) ;
  unselect () ;
}

//··································································································
//   MCP2515 controller state
//··································································································

uint8_t ACAN2515::transmitErrorCounter (void) {
  mSPI.beginTransaction (mSPISettings) ;
    const uint8_t result = read2515Register (TEC_REGISTER) ;
  mSPI.endTransaction () ;
  return result ;
}

//··································································································

uint8_t ACAN2515::receiveErrorCounter (void) {
  mSPI.beginTransaction (mSPISettings) ;
    const uint8_t result = read2515Register (REC_REGISTER) ;
  mSPI.endTransaction () ;
  return result ;
}

//··································································································

uint8_t ACAN2515::errorFlagRegister (void) {
  mSPI.beginTransaction (mSPISettings) ;
    const uint8_t result = read2515Register (EFLG_REGISTER) ;
  mSPI.endTransaction () ;
  return result ;
}

//··································································································
//   MESSAGE EMISSION
//··································································································

bool ACAN2515::sendBufferNotFullForIndex (const uint32_t inIndex) {
//--- Fix send buffer index
  uint8_t idx = inIndex ;
  if (idx > 2) {
    idx = 0 ;
  }
  #ifndef ARDUINO_ARCH_ESP32
    noInterrupts () ;
  #endif
    mSPI.beginTransaction (mSPISettings) ;
      const bool ok = mTXBIsFree [idx] || !mTransmitBuffer [idx].isFull () ;
    mSPI.endTransaction () ;
  #ifndef ARDUINO_ARCH_ESP32
    interrupts () ;
  #endif
  return ok ;
}

//··································································································

bool ACAN2515::tryToSend (const CANMessage & inMessage) {
//--- Fix send buffer index
  uint8_t idx = inMessage.idx ;
  if (idx > 2) {
    idx = 0 ;
  }
//--- Bug fix in 2.0.6 (thanks to Fergus Duncan): interrupts were only disabled for Teensy boards
  #ifndef ARDUINO_ARCH_ESP32
    noInterrupts () ;
  #endif
   //---
    mSPI.beginTransaction (mSPISettings) ;
      bool ok = mTXBIsFree [idx] ;
      if (ok) { // Transmit buffer and TXB are both free: transmit immediatly
        mTXBIsFree [idx] = false ;
        internalSendMessage (inMessage, idx) ;
      }else{ // Enter in transmit buffer, if not full
        ok = mTransmitBuffer [idx].append (inMessage) ;
      }
    mSPI.endTransaction () ;
  #ifndef ARDUINO_ARCH_ESP32
    interrupts () ;
  #endif
  return ok ;
}

//··································································································
