mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 10:19:29 +02:00
957 lines
42 KiB
C++
957 lines
42 KiB
C++
//··································································································
|
|
// 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 ;
|
|
}
|
|
|
|
//··································································································
|