diff --git a/Software/ACAN2515.cpp b/Software/ACAN2515.cpp new file mode 100644 index 00000000..34de3415 --- /dev/null +++ b/Software/ACAN2515.cpp @@ -0,0 +1,957 @@ +//·································································································· +// 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> 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 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 ; +} + +//·································································································· diff --git a/Software/ACAN2515.h b/Software/ACAN2515.h new file mode 100644 index 00000000..23db69cb --- /dev/null +++ b/Software/ACAN2515.h @@ -0,0 +1,280 @@ +//·································································································· +// A CAN driver for MCP2515 +// by Pierre Molinaro +// https://github.com/pierremolinaro/acan2515 +//·································································································· + +#pragma once + +//·································································································· + +#include "ACAN2515_Buffer16.h" +#include "ACAN2515Settings.h" +#include "MCP2515ReceiveFilters.h" +#include + +//·································································································· + +class ACAN2515 { + +//·································································································· +// Constructor: using hardware SPI +//·································································································· + + public: ACAN2515 (const uint8_t inCS, // CS input of MCP2515 + SPIClass & inSPI, // Hardware SPI object + const uint8_t inINT) ; // INT output of MCP2515 + + +//·································································································· +// Initialisation: returns 0 if ok, otherwise see error codes below +//·································································································· + + public: uint16_t begin (const ACAN2515Settings & inSettings, + void (* inInterruptServiceRoutine) (void)) ; + + public: uint16_t begin (const ACAN2515Settings & inSettings, + void (* inInterruptServiceRoutine) (void), + const ACAN2515Mask inRXM0, + const ACAN2515AcceptanceFilter inAcceptanceFilters [], + const uint8_t inAcceptanceFilterCount) ; + + public: uint16_t begin (const ACAN2515Settings & inSettings, + void (* inInterruptServiceRoutine) (void), + const ACAN2515Mask inRXM0, + const ACAN2515Mask inRXM1, + const ACAN2515AcceptanceFilter inAcceptanceFilters [], + const uint8_t inAcceptanceFilterCount) ; + +//·································································································· +// Error codes returned by begin +//·································································································· + + public: static const uint16_t kNoMCP2515 = 1 << 0 ; + public: static const uint16_t kTooFarFromDesiredBitRate = 1 << 1 ; + public: static const uint16_t kInconsistentBitRateSettings = 1 << 2 ; + public: static const uint16_t kINTPinIsNotAnInterrupt = 1 << 3 ; + public: static const uint16_t kISRIsNull = 1 << 4 ; + public: static const uint16_t kRequestedModeTimeOut = 1 << 5 ; + public: static const uint16_t kAcceptanceFilterArrayIsNULL = 1 << 6 ; + public: static const uint16_t kOneFilterMaskRequiresOneOrTwoAcceptanceFilters = 1 << 7 ; + public: static const uint16_t kTwoFilterMasksRequireThreeToSixAcceptanceFilters = 1 << 8 ; + public: static const uint16_t kCannotAllocateReceiveBuffer = 1 << 9 ; + public: static const uint16_t kCannotAllocateTransmitBuffer0 = 1 << 10 ; + public: static const uint16_t kCannotAllocateTransmitBuffer1 = 1 << 11 ; + public: static const uint16_t kCannotAllocateTransmitBuffer2 = 1 << 12 ; + public: static const uint32_t kISRNotNullAndNoIntPin = 1 << 13 ; + + +//·································································································· +// Change Mode on the fly +//·································································································· + + public: uint16_t changeModeOnTheFly (const ACAN2515Settings::RequestedMode inRequestedMode) ; + + +//·································································································· +// Set filters on the fly +//·································································································· + + public: uint16_t setFiltersOnTheFly (void) ; + + public: uint16_t setFiltersOnTheFly (const ACAN2515Mask inRXM0, + const ACAN2515AcceptanceFilter inAcceptanceFilters [], + const uint8_t inAcceptanceFilterCount) ; + + public: uint16_t setFiltersOnTheFly (const ACAN2515Mask inRXM0, + const ACAN2515Mask inRXM1, + const ACAN2515AcceptanceFilter inAcceptanceFilters [], + const uint8_t inAcceptanceFilterCount) ; + + +//·································································································· +// end +//·································································································· + + public: void end (void) ; + + +//·································································································· +// Receiving messages +//·································································································· + + public: bool available (void) ; + + public: bool receive (CANMessage & outFrame) ; + + public: typedef void (*tFilterMatchCallBack) (const uint8_t inFilterIndex) ; + + public: bool dispatchReceivedMessage (const tFilterMatchCallBack inFilterMatchCallBack = NULL) ; + + +//·································································································· +// Handling messages to send and receiving messages +//·································································································· + + public: void isr (void) ; + public: bool isr_core (void) ; + private: void handleTXBInterrupt (const uint8_t inTXB) ; + private: void handleRXBInterrupt (void) ; + + +//·································································································· +// Properties +//·································································································· + + private: SPIClass & mSPI ; + private: const SPISettings mSPISettings ; + private: const uint8_t mCS ; + private: const uint8_t mINT ; + private: bool mRolloverEnable ; + #ifdef ARDUINO_ARCH_ESP32 + public: SemaphoreHandle_t mISRSemaphore ; + private: void (* mInterruptServiceRoutine) (void) = nullptr ; + #endif + + +//·································································································· +// Receive buffer +//·································································································· + + private: ACAN2515_Buffer16 mReceiveBuffer ; + + +//·································································································· +// Receive buffer size +//·································································································· + + public: inline uint16_t receiveBufferSize (void) const { + return mReceiveBuffer.size () ; + } + + +//·································································································· +// Receive buffer count +//·································································································· + + public: inline uint16_t receiveBufferCount (void) const { + return mReceiveBuffer.count () ; + } + + +//·································································································· +// Receive buffer peak count +//·································································································· + + public: inline uint16_t receiveBufferPeakCount (void) const { + return mReceiveBuffer.peakCount () ; + } + + +//·································································································· +// Call back function array +//·································································································· + + private: ACANCallBackRoutine mCallBackFunctionArray [6] ; + + +//·································································································· +// Transmitting messages +//·································································································· + + public: bool sendBufferNotFullForIndex (const uint32_t inIndex) ; // 0 ... 2 + + public: bool tryToSend (const CANMessage & inMessage) ; + + +//·································································································· +// Driver transmit buffer +//·································································································· + + private: ACAN2515_Buffer16 mTransmitBuffer [3] ; + private: bool mTXBIsFree [3] ; + + public: inline uint16_t transmitBufferSize (const uint8_t inIndex) const { + return mTransmitBuffer [inIndex].size () ; + } + + public: inline uint16_t transmitBufferCount (const uint8_t inIndex) const { + return mTransmitBuffer [inIndex].count () ; + } + + public: inline uint16_t transmitBufferPeakCount (const uint8_t inIndex) const { + return mTransmitBuffer [inIndex].peakCount () ; + } + private: void internalSendMessage (const CANMessage & inFrame, const uint8_t inTXB) ; + + +//·································································································· +// Polling +//·································································································· + + public: void poll (void) ; + + +//·································································································· +// Private methods +//·································································································· + + private: inline void select (void) { digitalWrite (mCS, LOW) ; } + + private: inline void unselect (void) { digitalWrite (mCS, HIGH) ; } + + private: uint16_t beginWithoutFilterCheck (const ACAN2515Settings & inSettings, + void (* inInterruptServiceRoutine) (void), + const ACAN2515Mask inRXM0, + const ACAN2515Mask inRXM1, + const ACAN2515AcceptanceFilter inAcceptanceFilters [], + const uint8_t inAcceptanceFilterCount) ; + + private: uint16_t internalBeginOperation (const ACAN2515Settings & inSettings, + const ACAN2515Mask inRXM0, + const ACAN2515Mask inRXM1, + const ACAN2515AcceptanceFilter inAcceptanceFilters [], + const uint8_t inAcceptanceFilterCount) ; + + private: void write2515Register (const uint8_t inRegister, const uint8_t inValue) ; + + private: uint8_t read2515Register (const uint8_t inRegister) ; + + private: uint8_t read2515Status (void) ; + + private: uint8_t read2515RxStatus (void) ; + + private: void bitModify2515Register (const uint8_t inRegister, const uint8_t inMask, const uint8_t inData) ; + + private: void setupMaskRegister (const ACAN2515Mask inMask, const uint8_t inRegister) ; + + private: uint16_t setRequestedMode (const uint8_t inCANControlRegister) ; + + + private: uint16_t internalSetFiltersOnTheFly (const ACAN2515Mask inRXM0, + const ACAN2515Mask inRXM1, + const ACAN2515AcceptanceFilter inAcceptanceFilters [], + const uint8_t inAcceptanceFilterCount) ; + + #ifdef ARDUINO_ARCH_ESP32 + public: void attachMCP2515InterruptPin (void) ; + #endif + +//·································································································· +// MCP2515 controller state +//·································································································· + + public: uint8_t receiveErrorCounter (void) ; + public: uint8_t transmitErrorCounter (void) ; + public: uint8_t errorFlagRegister (void) ; + + +//·································································································· +// No Copy +//·································································································· + + private: ACAN2515 (const ACAN2515 &) = delete ; + private: ACAN2515 & operator = (const ACAN2515 &) = delete ; + +//·································································································· + +} ; + +//·································································································· diff --git a/Software/ACAN2515Settings.cpp b/Software/ACAN2515Settings.cpp new file mode 100644 index 00000000..1c7712fa --- /dev/null +++ b/Software/ACAN2515Settings.cpp @@ -0,0 +1,176 @@ +//·································································································· +// A CAN driver for MCP2515 +// by Pierre Molinaro +// https://github.com/pierremolinaro/acan2515 +//·································································································· + +#include "ACAN2515Settings.h" + +//·································································································· +// CAN Settings +//·································································································· + +ACAN2515Settings::ACAN2515Settings (const uint32_t inQuartzFrequency, + const uint32_t inWhishedBitRate, + const uint32_t inTolerancePPM) : +mQuartzFrequency (inQuartzFrequency) { + if (mDesiredBitRate != inWhishedBitRate) { + mDesiredBitRate = inWhishedBitRate ; + const uint32_t clock = mQuartzFrequency / 2 ; + uint32_t TQCount = 25 ; // TQCount: 5 ... 25 + uint32_t smallestError = UINT32_MAX ; + uint32_t bestBRP = 64 ; // Setting for slowest bit rate + uint32_t bestTQCount = 25 ; // Setting for slowest bit rate + uint32_t BRP = clock / inWhishedBitRate / TQCount ; + //--- Loop for finding best BRP and best TQCount + while ((TQCount >= 5) && (BRP <= 64)) { + //--- Compute error using BRP (caution: BRP should be > 0) + if (BRP > 0) { + const uint32_t error = clock - inWhishedBitRate * TQCount * BRP ; // error is always >= 0 + if (error < smallestError) { + smallestError = error ; + bestBRP = BRP ; + bestTQCount = TQCount ; + } + } + //--- Compute error using BRP+1 (caution: BRP+1 should be <= 64) + if (BRP < 64) { + const uint32_t error = inWhishedBitRate * TQCount * (BRP + 1) - clock ; // error is always >= 0 + if (error < smallestError) { + smallestError = error ; + bestBRP = BRP + 1 ; + bestTQCount = TQCount ; + } + } + //--- Continue with next value of TQCount + TQCount -- ; + BRP = clock / (inWhishedBitRate * TQCount) ; + } + //--- Set the BRP + mBitRatePrescaler = (uint8_t) bestBRP ; + //--- Compute PS2 + const uint32_t PS2 = (bestTQCount + 1) / 3 ; // Always 2 <= PS2 <= 8 + mPhaseSegment2 = (uint8_t) PS2 ; + //--- Compute the remaining number of TQ once PS2 and SyncSeg are removed + const uint32_t propSegmentPlusPhaseSegment1 = bestTQCount - PS2 - 1 /* Sync Seg */ ; + //--- Set PS1 to half of remaining TQCount + const uint32_t PS1 = propSegmentPlusPhaseSegment1 / 2 ; // Always 1 <= PS1 <= 8 + mPhaseSegment1 = (uint8_t) PS1 ; + //--- Set PS to what is left + mPropagationSegment = (uint8_t) (propSegmentPlusPhaseSegment1 - PS1) ; // Always 1 <= PropSeg <= 8 + //--- Set SJW to PS2, with a maximum value of 4 + mSJW = (mPhaseSegment2 > 4) ? 4 : (mPhaseSegment2 - 1) ; // Always 2 <= RJW <= 4, and RJW <= mPhaseSegment2 + //--- Triple sampling ? + mTripleSampling = (inWhishedBitRate <= 125000) && (mPhaseSegment1 >= 2) ; + //--- Final check of the configuration + const uint32_t W = bestTQCount * mDesiredBitRate * mBitRatePrescaler ; + const uint64_t diff = (clock > W) ? (clock - W) : (W - clock) ; + const uint64_t ppm = (uint64_t) (1000UL * 1000UL) ; // UL suffix is required for Arduino Uno + mBitRateClosedToDesiredRate = (diff * ppm) <= (((uint64_t) W) * inTolerancePPM) ; + } +} ; + +//·································································································· + +static uint32_t bitrateFrom (const uint32_t inQuartzFrequency, // In Hertz + const uint8_t inBitRatePrescaler, // 1...64 + const uint8_t inPropagationSegment, // 1...8 + const uint8_t inPhaseSegment1, // 1...8 + const uint8_t inPhaseSegment2) {// 2...8 + + const uint8_t TQ = 1 + inPropagationSegment + inPhaseSegment1 + inPhaseSegment2 ; + return inQuartzFrequency / inBitRatePrescaler / TQ / 2 ; +} + +//·································································································· + +ACAN2515Settings::ACAN2515Settings (const uint32_t inQuartzFrequency, // In Hertz + const uint8_t inBitRatePrescaler, // 1...64 + const uint8_t inPropagationSegment, // 1...8 + const uint8_t inPhaseSegment1, // 1...8 + const uint8_t inPhaseSegment2, // 2...8 + const uint8_t inSJW) : // 1...4 +mQuartzFrequency (inQuartzFrequency), +mDesiredBitRate (bitrateFrom (inQuartzFrequency, inBitRatePrescaler, inPropagationSegment, inPhaseSegment1, inPhaseSegment2)), +mPropagationSegment (inPropagationSegment), +mPhaseSegment1 (inPhaseSegment1), +mPhaseSegment2 (inPhaseSegment2), +mSJW (inSJW), +mBitRatePrescaler (inBitRatePrescaler), +mBitRateClosedToDesiredRate (true) { +} ; + +//·································································································· + +uint32_t ACAN2515Settings::actualBitRate (void) const { + const uint32_t TQCount = 1 /* Sync Seg */ + mPropagationSegment + mPhaseSegment1 + mPhaseSegment2 ; + return mQuartzFrequency / mBitRatePrescaler / TQCount / 2 ; +} + +//·································································································· + +bool ACAN2515Settings::exactBitRate (void) const { + const uint32_t TQCount = 1 /* Sync Seg */ + mPropagationSegment + mPhaseSegment1 + mPhaseSegment2 ; + return mQuartzFrequency == (mDesiredBitRate * mBitRatePrescaler * TQCount * 2) ; +} + +//·································································································· + +uint32_t ACAN2515Settings::ppmFromDesiredBitRate (void) const { + const uint32_t TQCount = 1 /* Sync Seg */ + mPropagationSegment + mPhaseSegment1 + mPhaseSegment2 ; + const uint32_t W = TQCount * mDesiredBitRate * mBitRatePrescaler * 2 ; + const uint64_t diff = (mQuartzFrequency > W) ? (mQuartzFrequency - W) : (W - mQuartzFrequency) ; + const uint64_t ppm = (uint64_t) (1000UL * 1000UL) ; // UL suffix is required for Arduino Uno + return (uint32_t) ((diff * ppm) / W) ; +} + +//·································································································· + +uint32_t ACAN2515Settings::samplePointFromBitStart (void) const { + const uint32_t TQCount = 1 /* Sync Seg */ + mPropagationSegment + mPhaseSegment1 + mPhaseSegment2 ; + const uint32_t samplePoint = 1 /* Sync Seg */ + mPropagationSegment + mPhaseSegment1 - mTripleSampling ; + const uint32_t partPerCent = 100 ; + return (samplePoint * partPerCent) / TQCount ; +} + +//·································································································· + +uint16_t ACAN2515Settings::CANBitSettingConsistency (void) const { + uint16_t errorCode = 0 ; // Means no error + if (mBitRatePrescaler == 0) { + errorCode |= kBitRatePrescalerIsZero ; + }else if (mBitRatePrescaler > 64) { + errorCode |= kBitRatePrescalerIsGreaterThan64 ; + } + if (mPropagationSegment == 0) { + errorCode |= kPropagationSegmentIsZero ; + }else if (mPropagationSegment > 8) { + errorCode |= kPropagationSegmentIsGreaterThan8 ; + } + if (mPhaseSegment1 == 0) { + errorCode |= kPhaseSegment1IsZero ; + }else if ((mPhaseSegment1 == 1) && mTripleSampling) { + errorCode |= kPhaseSegment1Is1AndTripleSampling ; + }else if (mPhaseSegment1 > 8) { + errorCode |= kPhaseSegment1IsGreaterThan8 ; + } + if (mPhaseSegment2 < 2) { + errorCode |= kPhaseSegment2IsLowerThan2 ; + }else if (mPhaseSegment2 > 8) { + errorCode |= kPhaseSegment2IsGreaterThan8 ; + } + if (mSJW == 0) { + errorCode |= kSJWIsZero ; + }else if (mSJW > 4) { + errorCode |= kSJWIsGreaterThan4 ; + } + if (mSJW >= mPhaseSegment2) { + errorCode |= kSJWIsGreaterThanOrEqualToPhaseSegment2 ; + } + if (mPhaseSegment2 > (mPropagationSegment + mPhaseSegment1)) { + errorCode |= kPhaseSegment2IsGreaterThanPSPlusPS1 ; + } + return errorCode ; +} + +//·································································································· diff --git a/Software/ACAN2515Settings.h b/Software/ACAN2515Settings.h new file mode 100644 index 00000000..a215ccde --- /dev/null +++ b/Software/ACAN2515Settings.h @@ -0,0 +1,182 @@ +//·································································································· +// A CAN driver for MCP2515 +// by Pierre Molinaro +// https://github.com/pierremolinaro/acan2515 +//·································································································· + +#pragma once + +//·································································································· + +#include + +//·································································································· + +class ACAN2515Settings { + +//·································································································· +// Enumerations +//·································································································· + + public: typedef enum : uint8_t { + NormalMode = 0 << 5, + SleepMode = 1 << 5, + LoopBackMode = 2 << 5, + ListenOnlyMode = 3 << 5 + } RequestedMode ; + +//·································································································· + + public: typedef enum : uint8_t {CLOCK, CLOCK2, CLOCK4, CLOCK8, SOF, HiZ} CLKOUT_SOF ; + +//·································································································· +// Constructor for a given baud rate +//·································································································· + + public: explicit ACAN2515Settings (const uint32_t inQuartzFrequency, // In Hertz + const uint32_t inDesiredBitRate, + const uint32_t inTolerancePPM = 1000) ; +//·································································································· +// Constructor with explicit bit settings +//·································································································· + + public: explicit ACAN2515Settings (const uint32_t inQuartzFrequency, // In Hertz + const uint8_t inBitRatePrescaler, // 1...64 + const uint8_t inPropagationSegment, // 1...8 + const uint8_t inPhaseSegment1, // 1...8 + const uint8_t inPhaseSegment2, // 2...8 + const uint8_t inSJW) ; // 1...4 + +//·································································································· +// CAN bit timing properties +//·································································································· + + public: const uint32_t mQuartzFrequency ; + public: uint32_t mDesiredBitRate = mQuartzFrequency / 64 ; // In kb/s + public: uint8_t mPropagationSegment = 5 ; // 1...8 + public: uint8_t mPhaseSegment1 = 5 ; // 1...8 + public: uint8_t mPhaseSegment2 = 5 ; // 2...8 + public: uint8_t mSJW = 4 ; // 1...4 + public: uint8_t mBitRatePrescaler = 32 / (1 + mPropagationSegment + mPhaseSegment1 + mPhaseSegment2) ; // 1...64 + public: bool mTripleSampling = false ; // true --> triple sampling, false --> single sampling + public: bool mBitRateClosedToDesiredRate = true ; // The above configuration is correct + + +//·································································································· +// One shot mode +// true --> Enabled; messages will only attempt to transmit one time +// false --> Disabled; messages will reattempt transmission if required +//·································································································· + + public: bool mOneShotModeEnabled = false ; + + +//·································································································· +// MCP2515 TXBi priorities +// bits 7-6: unused +// bits 5-4: TXB2 priority +// bits 3-2: TXB1 priority +// bits 1-0: TXB0 priority +//·································································································· + + public: uint8_t mTXBPriority = 0 ; + + +//·································································································· +// Requested mode +//·································································································· + + public: RequestedMode mRequestedMode = NormalMode ; + + +//·································································································· +// Signal on CLKOUT/SOF pin +//·································································································· + + public: CLKOUT_SOF mCLKOUT_SOF_pin = CLOCK ; + + +//·································································································· +// Rollover Enable Bit (is set to the BUKT bit of the RXB0CTRL register) +// true --> RXB0 message will roll over and be written to RXB1 if RXB0 is full +// false --> Rollover is disabled +//·································································································· + + public : bool mRolloverEnable = true ; + + +//·································································································· +// Receive buffer size +//·································································································· + + public: uint16_t mReceiveBufferSize = 32 ; + + +//·································································································· +// Transmit buffer sizes +//·································································································· + + public: uint16_t mTransmitBuffer0Size = 16 ; + public: uint16_t mTransmitBuffer1Size = 0 ; + public: uint16_t mTransmitBuffer2Size = 0 ; + + +//·································································································· +// Compute actual bit rate +//·································································································· + + public: uint32_t actualBitRate (void) const ; + + +//·································································································· +// Exact bit rate ? +//·································································································· + + public: bool exactBitRate (void) const ; + + +//·································································································· +// Distance between actual bit rate and requested bit rate (in ppm, part-per-million) +//·································································································· + + public: uint32_t ppmFromDesiredBitRate (void) const ; + + +//·································································································· +// Distance of sample point from bit start (in ppc, part-per-cent, denoted by %) +//·································································································· + + public: uint32_t samplePointFromBitStart (void) const ; + + +//·································································································· +// Bit settings are consistent ? (returns 0 if ok) +//·································································································· + + public: uint16_t CANBitSettingConsistency (void) const ; + + +//·································································································· +// Constants returned by CANBitSettingConsistency +//·································································································· + + public: static const uint16_t kBitRatePrescalerIsZero = 1 << 0 ; + public: static const uint16_t kBitRatePrescalerIsGreaterThan64 = 1 << 1 ; + public: static const uint16_t kPropagationSegmentIsZero = 1 << 2 ; + public: static const uint16_t kPropagationSegmentIsGreaterThan8 = 1 << 3 ; + public: static const uint16_t kPhaseSegment1IsZero = 1 << 4 ; + public: static const uint16_t kPhaseSegment1IsGreaterThan8 = 1 << 5 ; + public: static const uint16_t kPhaseSegment2IsLowerThan2 = 1 << 6 ; + public: static const uint16_t kPhaseSegment2IsGreaterThan8 = 1 << 7 ; + public: static const uint16_t kPhaseSegment1Is1AndTripleSampling = 1 << 8 ; + public: static const uint16_t kSJWIsZero = 1 << 9 ; + public: static const uint16_t kSJWIsGreaterThan4 = 1 << 10 ; + public: static const uint16_t kSJWIsGreaterThanOrEqualToPhaseSegment2 = 1 << 11 ; + public: static const uint16_t kPhaseSegment2IsGreaterThanPSPlusPS1 = 1 << 12 ; + +//·································································································· + +} ; + +//·································································································· + diff --git a/Software/ACAN2515_Buffer16.h b/Software/ACAN2515_Buffer16.h new file mode 100644 index 00000000..c864c32c --- /dev/null +++ b/Software/ACAN2515_Buffer16.h @@ -0,0 +1,130 @@ +//-------------------------------------------------------------------------------------------------- + +#pragma once + +//-------------------------------------------------------------------------------------------------- + +#include "CANMessage.h" + +//-------------------------------------------------------------------------------------------------- + +class ACAN2515_Buffer16 { + + //································································································ + // Default constructor + //································································································ + + public: ACAN2515_Buffer16 (void) : + mBuffer (NULL), + mSize (0), + mReadIndex (0), + mCount (0), + mPeakCount (0) { + } + + //································································································ + // Destructor + //································································································ + + public: ~ ACAN2515_Buffer16 (void) { + delete [] mBuffer ; + } + + //································································································ + // Private properties + //································································································ + + private: CANMessage * mBuffer ; + private: uint16_t mSize ; + private: uint16_t mReadIndex ; + private: uint16_t mCount ; + private: uint16_t mPeakCount ; // > mSize if overflow did occur + + //································································································ + // Accessors + //································································································ + + public: inline bool isFull (void) const { return mCount == mSize ; } + public: inline uint16_t size (void) const { return mSize ; } + public: inline uint16_t count (void) const { return mCount ; } + public: inline uint16_t peakCount (void) const { return mPeakCount ; } + + //································································································ + // initWithSize + //································································································ + + public: bool initWithSize (const uint16_t inSize) { + delete [] mBuffer ; + mBuffer = new CANMessage [inSize] ; + const bool ok = mBuffer != NULL ; + mSize = ok ? inSize : 0 ; + mReadIndex = 0 ; + mCount = 0 ; + mPeakCount = 0 ; + return ok ; + } + + //································································································ + // append + //································································································ + + public: bool append (const CANMessage & inMessage) { + const bool ok = mCount < mSize ; + if (ok) { + uint16_t writeIndex = mReadIndex + mCount ; + if (writeIndex >= mSize) { + writeIndex -= mSize ; + } + mBuffer [writeIndex] = inMessage ; + mCount ++ ; + if (mPeakCount < mCount) { + mPeakCount = mCount ; + } + } + return ok ; + } + + //································································································ + // Remove + //································································································ + + public: bool remove (CANMessage & outMessage) { + const bool ok = mCount > 0 ; + if (ok) { + outMessage = mBuffer [mReadIndex] ; + mCount -= 1 ; + mReadIndex += 1 ; + if (mReadIndex == mSize) { + mReadIndex = 0 ; + } + } + return ok ; + } + + //································································································ + // Free + //································································································ + + public: void free (void) { + delete [] mBuffer ; mBuffer = nullptr ; + mSize = 0 ; + mReadIndex = 0 ; + mCount = 0 ; + mPeakCount = 0 ; + } + + //································································································ + // Reset Peak Count + //································································································ + + public: inline void resetPeakCount (void) { mPeakCount = mCount ; } + + //································································································ + // No copy + //································································································ + + private: ACAN2515_Buffer16 (const ACAN2515_Buffer16 &) ; + private: ACAN2515_Buffer16 & operator = (const ACAN2515_Buffer16 &) ; +} ; + +//-------------------------------------------------------------------------------------------------- diff --git a/Software/ACANBuffer.h b/Software/ACANBuffer.h new file mode 100644 index 00000000..7e7a67a0 --- /dev/null +++ b/Software/ACANBuffer.h @@ -0,0 +1,117 @@ +//·································································································· +// This file is not used any more by ACAN2515 driver. +// It is provided for compatibility with sketchs that use it. +//·································································································· + +#ifndef ACAN_BUFFER_CLASS_DEFINED +#define ACAN_BUFFER_CLASS_DEFINED + +//·································································································· + +#include + +//·································································································· + +class ACANBuffer { + +//·································································································· +// Default constructor +//·································································································· + + public: ACANBuffer (void) : + mBuffer (NULL), + mSize (0), + mReadIndex (0), + mWriteIndex (0), + mCount (0), + mPeakCount (0) { + } + +//·································································································· +// Destructor +//·································································································· + + public: ~ ACANBuffer (void) { + delete [] mBuffer ; + } + +//·································································································· +// Private properties +//·································································································· + + private: CANMessage * mBuffer ; + private: uint32_t mSize ; + private: uint32_t mReadIndex ; + private: uint32_t mWriteIndex ; + private: uint32_t mCount ; + private: uint32_t mPeakCount ; // > mSize if overflow did occur + +//·································································································· +// Accessors +//·································································································· + + public: inline uint32_t size (void) const { return mSize ; } + public: inline uint32_t count (void) const { return mCount ; } + public: inline uint32_t peakCount (void) const { return mPeakCount ; } + +//·································································································· +// initWithSize +//·································································································· + + public: void initWithSize (const uint32_t inSize) { + mBuffer = new CANMessage [inSize] ; + mSize = inSize ; + mReadIndex = 0 ; + mWriteIndex = 0 ; + mCount = 0 ; + mPeakCount = 0 ; + } + +//·································································································· +// append +//·································································································· + + public: bool append (const CANMessage & inMessage) { + const bool ok = mCount < mSize ; + if (ok) { + mBuffer [mWriteIndex] = inMessage ; + mWriteIndex += 1 ; + if (mWriteIndex == mSize) { + mWriteIndex = 0 ; + } + mCount ++ ; + if (mPeakCount < mCount) { + mPeakCount = mCount ; + } + } + return ok ; + } + +//·································································································· +// Remove +//·································································································· + + public: bool remove (CANMessage & outMessage) { + const bool ok = mCount > 0 ; + if (ok) { + outMessage = mBuffer [mReadIndex] ; + mCount -= 1 ; + mReadIndex += 1 ; + if (mReadIndex == mSize) { + mReadIndex = 0 ; + } + } + return ok ; + } + +//·································································································· +// No copy +//·································································································· + + private: ACANBuffer (const ACANBuffer &) ; + private: ACANBuffer & operator = (const ACANBuffer &) ; +} ; + +//·································································································· + +#endif diff --git a/Software/CANMessage.h b/Software/CANMessage.h new file mode 100644 index 00000000..6c687d7e --- /dev/null +++ b/Software/CANMessage.h @@ -0,0 +1,49 @@ +//---------------------------------------------------------------------------------------------------------------------- +// Generic CAN Message +// by Pierre Molinaro +// +// This file is common to the following libraries +// https://github.com/pierremolinaro/acan +// https://github.com/pierremolinaro/acan2515 +// https://github.com/pierremolinaro/acan2517 +// https://github.com/pierremolinaro/acan2517FD +// +//---------------------------------------------------------------------------------------------------------------------- + +#ifndef GENERIC_CAN_MESSAGE_DEFINED +#define GENERIC_CAN_MESSAGE_DEFINED + +//---------------------------------------------------------------------------------------------------------------------- + +#include + +//---------------------------------------------------------------------------------------------------------------------- + +class CANMessage { + public : uint32_t id = 0 ; // Frame identifier + public : bool ext = false ; // false -> standard frame, true -> extended frame + public : bool rtr = false ; // false -> data frame, true -> remote frame + public : uint8_t idx = 0 ; // This field is used by the driver + public : uint8_t len = 0 ; // Length of data (0 ... 8) + public : union { + uint64_t data64 ; // Caution: subject to endianness + int64_t data_s64 ; // Caution: subject to endianness + uint32_t data32 [2] ; // Caution: subject to endianness + int32_t data_s32 [2] ; // Caution: subject to endianness + float dataFloat [2] ; // Caution: subject to endianness + uint16_t data16 [4] ; // Caution: subject to endianness + int16_t data_s16 [4] ; // Caution: subject to endianness + int8_t data_s8 [8] ; + uint8_t data [8] = {0, 0, 0, 0, 0, 0, 0, 0} ; + } ; +} ; + +//---------------------------------------------------------------------------------------------------------------------- + +typedef enum {kStandard, kExtended} tFrameFormat ; +typedef enum {kData, kRemote} tFrameKind ; +typedef void (*ACANCallBackRoutine) (const CANMessage & inMessage) ; + +//---------------------------------------------------------------------------------------------------------------------- + +#endif diff --git a/Software/MCP2515ReceiveFilters.h b/Software/MCP2515ReceiveFilters.h new file mode 100644 index 00000000..13b8355a --- /dev/null +++ b/Software/MCP2515ReceiveFilters.h @@ -0,0 +1,91 @@ +//·································································································· +// MCP2515 Receive filter classes +// by Pierre Molinaro +// https://github.com/pierremolinaro/acan2515 +//·································································································· + +#ifndef MCP2515_RECEIVE_FILTER_ENTITIES_DEFINED +#define MCP2515_RECEIVE_FILTER_ENTITIES_DEFINED + +//·································································································· + +#include + +//·································································································· + +class ACAN2515Mask { + +//--- Default constructor + public: ACAN2515Mask (void) : + mSIDH (0), + mSIDL (0), + mEID8 (0), + mEID0 (0) { + } + +//--- Properties + public: uint8_t mSIDH ; + public: uint8_t mSIDL ; + public: uint8_t mEID8 ; + public: uint8_t mEID0 ; +} ; + +//·································································································· + +class ACAN2515AcceptanceFilter { + public: typedef void (*tCallBackRoutine) (const CANMessage & inMessage) ; + public: const ACAN2515Mask mMask ; + public: const tCallBackRoutine mCallBack ; +} ; + +//·································································································· + +inline ACAN2515Mask standard2515Mask (const uint16_t inIdentifier, + const uint8_t inByte0, + const uint8_t inByte1) { + ACAN2515Mask result ; + result.mSIDH = (uint8_t) (inIdentifier >> 3) ; + result.mSIDL = (uint8_t) (inIdentifier << 5) ; + result.mEID8 = inByte0 ; + result.mEID0 = inByte1 ; + return result ; +} + +//·································································································· + +inline ACAN2515Mask extended2515Mask (const uint32_t inIdentifier) { + ACAN2515Mask result ; + result.mSIDH = (uint8_t) (inIdentifier >> 21) ; + result.mSIDL = (uint8_t) (((inIdentifier >> 16) & 0x03) | ((inIdentifier >> 13) & 0xE0)) ; + result.mEID8 = (uint8_t) (inIdentifier >> 8) ; + result.mEID0 = (uint8_t) inIdentifier ; + return result ; +} + +//·································································································· + +inline ACAN2515Mask standard2515Filter (const uint16_t inIdentifier, + const uint8_t inByte0, + const uint8_t inByte1) { + ACAN2515Mask result ; + result.mSIDH = (uint8_t) (inIdentifier >> 3) ; + result.mSIDL = (uint8_t) (inIdentifier << 5) ; + result.mEID8 = inByte0 ; + result.mEID0 = inByte1 ; + return result ; +} + +//·································································································· + +inline ACAN2515Mask extended2515Filter (const uint32_t inIdentifier) { + ACAN2515Mask result ; + result.mSIDH = (uint8_t) (inIdentifier >> 21) ; + result.mSIDL = (uint8_t) (((inIdentifier >> 16) & 0x03) | ((inIdentifier >> 13) & 0xE0)) | 0x08 ; + result.mEID8 = (uint8_t) (inIdentifier >> 8) ; + result.mEID0 = (uint8_t) inIdentifier ; + return result ; +} + +//·································································································· + +#endif diff --git a/Software/SOLAX-CAN.cpp b/Software/SOLAX-CAN.cpp index e4acee09..16581bf2 100644 --- a/Software/SOLAX-CAN.cpp +++ b/Software/SOLAX-CAN.cpp @@ -30,7 +30,7 @@ CAN_frame_t SOLAX_100A001 = {.FIR = {.B = {.DLC = 0,.FF = CAN_frame_ext,}},.MsgI void CAN_WriteFrame(CAN_frame_t* tx_frame) { -#ifdef DUAL_CAN +if(dual_can){ CANMessage MCP2515Frame; //Struct with ACAN2515 library format, needed to use the MCP2515 library MCP2515Frame.id = tx_frame->MsgID; MCP2515Frame.ext = tx_frame->FIR.B.FF; @@ -39,11 +39,10 @@ void CAN_WriteFrame(CAN_frame_t* tx_frame) MCP2515Frame.data[i] = tx_frame->data.u8[i]; } can.tryToSend(MCP2515Frame); - //Serial.println("Solax CAN Frame sent in Bus 2"); -#else + } + else{ ESP32Can.CANWriteFrame(tx_frame); - //Serial.println("Solax CAN Frame sent in Bus 1"); -#endif + } } void update_values_can_solax() diff --git a/Software/SOLAX-CAN.h b/Software/SOLAX-CAN.h index 42bff38b..81e2dfa3 100644 --- a/Software/SOLAX-CAN.h +++ b/Software/SOLAX-CAN.h @@ -4,12 +4,9 @@ #include "ESP32CAN.h" #include "config.h" -//#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 controller (Needed for FoxESS inverters) - -#ifdef DUAL_CAN -#include "ACAN2515.h" //If this file is missing, install the ACAN2515 library +#include "ACAN2515.h" extern ACAN2515 can; -#endif +extern bool dual_can; extern uint16_t SOC; extern uint16_t StateOfHealth; diff --git a/Software/Software.ino b/Software/Software.ino index 14e4e78c..1f9bf32d 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -36,11 +36,15 @@ //CAN parameters CAN_device_t CAN_cfg; // CAN Config const int rx_queue_size = 10; // Receive Queue size + #ifdef DUAL_CAN - #include + const bool dual_can = 1; + #include "ACAN2515.h" static const uint32_t QUARTZ_FREQUENCY = 8UL * 1000UL * 1000UL ; // 8 MHz ACAN2515 can(MCP2515_CS, SPI, MCP2515_INT); static ACAN2515_Buffer16 gBuffer; +#else + const bool dual_can = 0; #endif //Interval settings