mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 18:29:48 +02:00
Fix multiple defines, add ACAN locally
This commit is contained in:
parent
284c32cda8
commit
188812a565
11 changed files with 1993 additions and 11 deletions
957
Software/ACAN2515.cpp
Normal file
957
Software/ACAN2515.cpp
Normal file
|
@ -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<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 ;
|
||||||
|
}
|
||||||
|
|
||||||
|
//··································································································
|
280
Software/ACAN2515.h
Normal file
280
Software/ACAN2515.h
Normal file
|
@ -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 <SPI.h>
|
||||||
|
|
||||||
|
//··································································································
|
||||||
|
|
||||||
|
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 ;
|
||||||
|
|
||||||
|
//··································································································
|
||||||
|
|
||||||
|
} ;
|
||||||
|
|
||||||
|
//··································································································
|
176
Software/ACAN2515Settings.cpp
Normal file
176
Software/ACAN2515Settings.cpp
Normal file
|
@ -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 ;
|
||||||
|
}
|
||||||
|
|
||||||
|
//··································································································
|
182
Software/ACAN2515Settings.h
Normal file
182
Software/ACAN2515Settings.h
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
//··································································································
|
||||||
|
// A CAN driver for MCP2515
|
||||||
|
// by Pierre Molinaro
|
||||||
|
// https://github.com/pierremolinaro/acan2515
|
||||||
|
//··································································································
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
//··································································································
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
//··································································································
|
||||||
|
|
||||||
|
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 ;
|
||||||
|
|
||||||
|
//··································································································
|
||||||
|
|
||||||
|
} ;
|
||||||
|
|
||||||
|
//··································································································
|
||||||
|
|
130
Software/ACAN2515_Buffer16.h
Normal file
130
Software/ACAN2515_Buffer16.h
Normal file
|
@ -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 &) ;
|
||||||
|
} ;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
117
Software/ACANBuffer.h
Normal file
117
Software/ACANBuffer.h
Normal file
|
@ -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 <CANMessage.h>
|
||||||
|
|
||||||
|
//··································································································
|
||||||
|
|
||||||
|
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
|
49
Software/CANMessage.h
Normal file
49
Software/CANMessage.h
Normal file
|
@ -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 <Arduino.h>
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
91
Software/MCP2515ReceiveFilters.h
Normal file
91
Software/MCP2515ReceiveFilters.h
Normal file
|
@ -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 <Arduino.h>
|
||||||
|
|
||||||
|
//··································································································
|
||||||
|
|
||||||
|
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
|
|
@ -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)
|
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
|
CANMessage MCP2515Frame; //Struct with ACAN2515 library format, needed to use the MCP2515 library
|
||||||
MCP2515Frame.id = tx_frame->MsgID;
|
MCP2515Frame.id = tx_frame->MsgID;
|
||||||
MCP2515Frame.ext = tx_frame->FIR.B.FF;
|
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];
|
MCP2515Frame.data[i] = tx_frame->data.u8[i];
|
||||||
}
|
}
|
||||||
can.tryToSend(MCP2515Frame);
|
can.tryToSend(MCP2515Frame);
|
||||||
//Serial.println("Solax CAN Frame sent in Bus 2");
|
}
|
||||||
#else
|
else{
|
||||||
ESP32Can.CANWriteFrame(tx_frame);
|
ESP32Can.CANWriteFrame(tx_frame);
|
||||||
//Serial.println("Solax CAN Frame sent in Bus 1");
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_values_can_solax()
|
void update_values_can_solax()
|
||||||
|
|
|
@ -4,12 +4,9 @@
|
||||||
#include "ESP32CAN.h"
|
#include "ESP32CAN.h"
|
||||||
#include "config.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)
|
#include "ACAN2515.h"
|
||||||
|
|
||||||
#ifdef DUAL_CAN
|
|
||||||
#include "ACAN2515.h" //If this file is missing, install the ACAN2515 library
|
|
||||||
extern ACAN2515 can;
|
extern ACAN2515 can;
|
||||||
#endif
|
extern bool dual_can;
|
||||||
|
|
||||||
extern uint16_t SOC;
|
extern uint16_t SOC;
|
||||||
extern uint16_t StateOfHealth;
|
extern uint16_t StateOfHealth;
|
||||||
|
|
|
@ -36,11 +36,15 @@
|
||||||
//CAN parameters
|
//CAN parameters
|
||||||
CAN_device_t CAN_cfg; // CAN Config
|
CAN_device_t CAN_cfg; // CAN Config
|
||||||
const int rx_queue_size = 10; // Receive Queue size
|
const int rx_queue_size = 10; // Receive Queue size
|
||||||
|
|
||||||
#ifdef DUAL_CAN
|
#ifdef DUAL_CAN
|
||||||
#include <ACAN2515.h>
|
const bool dual_can = 1;
|
||||||
|
#include "ACAN2515.h"
|
||||||
static const uint32_t QUARTZ_FREQUENCY = 8UL * 1000UL * 1000UL ; // 8 MHz
|
static const uint32_t QUARTZ_FREQUENCY = 8UL * 1000UL * 1000UL ; // 8 MHz
|
||||||
ACAN2515 can(MCP2515_CS, SPI, MCP2515_INT);
|
ACAN2515 can(MCP2515_CS, SPI, MCP2515_INT);
|
||||||
static ACAN2515_Buffer16 gBuffer;
|
static ACAN2515_Buffer16 gBuffer;
|
||||||
|
#else
|
||||||
|
const bool dual_can = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//Interval settings
|
//Interval settings
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue