Skip to content

Commit

Permalink
Merge pull request #10 from zkemble/master
Browse files Browse the repository at this point in the history
Improved sleep functionality
  • Loading branch information
coryjfowler committed Mar 17, 2021
2 parents c538b54 + dfd5449 commit 43dc655
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 47 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -24,6 +24,8 @@ Using the setMode() function the sketch can now put the protocol controller into

User can enable and disable (default) One-Shot transmission mode from the sketch using enOneShotTX() or disOneShotTX() respectively.

To wake up from CAN bus activity while in sleep mode enable the wake up interrupt with setSleepWakeup(1). Passing 0 will disable the wakeup interrupt (default).

Installation
==============
Copy this into the "[.../MySketches/]libraries/" folder and restart the Arduino editor.
Expand Down
107 changes: 107 additions & 0 deletions examples/CAN_sleep/CAN_sleep.ino
@@ -0,0 +1,107 @@
// CAN Sleep Example
// By Zak Kemble, based on the CAN receive example

// If you only have 2 devices on the CAN bus (the device running this sketch and some other device sending messages), then you may find that duplicate messages are received when waking up.
// This is because when the MCP2515 wakes up it enters LISTENONLY mode where it does not send ACKs to messages, so the transmitter will retransmit the same message a few times.

#include <mcp_can.h>
#include <SPI.h>
#include <avr/sleep.h>

#define CAN0_INT 2 // Set INT to pin 2
MCP_CAN CAN0(10); // Set CS to pin 10

void setup()
{
Serial.begin(115200);

// Initialize MCP2515 running at 16MHz with a baudrate of 500kb/s and the masks and filters disabled.
if(CAN0.begin(MCP_ANY, CAN_500KBPS, MCP_16MHZ) == CAN_OK)
Serial.println(F("MCP2515 Initialized Successfully!"));
else
Serial.println(F("Error Initializing MCP2515..."));

CAN0.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data.
CAN0.setSleepWakeup(1); // Enable wake up interrupt when in sleep mode

pinMode(CAN0_INT, INPUT); // Configuring pin for /INT input

// Enable interrupts for the CAN0_INT pin (should be pin 2 or 3 for Uno and other ATmega328P based boards)
attachInterrupt(digitalPinToInterrupt(CAN0_INT), ISR_CAN, FALLING);

set_sleep_mode(SLEEP_MODE_PWR_DOWN);

Serial.println(F("MCP2515 Library Sleep Example..."));
}

void loop()
{
if(!digitalRead(CAN0_INT)) // If CAN0_INT pin is low, read receive buffer
{
unsigned long rxId;
byte len;
byte rxBuf[8];

if(CAN0.readMsgBuf(&rxId, &len, rxBuf) == CAN_OK) // Read data: len = data length, buf = data byte(s)
{
char msgString[128]; // Array to store serial string
if(rxId & CAN_IS_EXTENDED) // Determine if ID is standard (11 bits) or extended (29 bits)
sprintf_P(msgString, PSTR("Extended ID: 0x%.8lX DLC: %1d Data:"), (rxId & CAN_EXTENDED_ID), len);
else
sprintf_P(msgString, PSTR("Standard ID: 0x%.3lX DLC: %1d Data:"), rxId, len);

Serial.print(msgString);

if(rxId & CAN_IS_REMOTE_REQUEST) // Determine if message is a remote request frame.
Serial.print(F(" REMOTE REQUEST FRAME"));
else
{
for(byte i=0;i<len;i++)
{
sprintf_P(msgString, PSTR(" 0x%.2X"), rxBuf[i]);
Serial.print(msgString);
}
}

Serial.println();
}
else
Serial.println(F("Interrupt is asserted, but there were no messages to process..."));
}

Serial.println(F("SLEEPING..."));

// Put MCP2515 into sleep mode
// This can sometimes take upto around 100ms depending on what the chip is currently doing
CAN0.setMode(MCP_SLEEP);

Serial.println(F("SLEEP"));

// Clear serial buffers before sleeping
Serial.flush();

// Put the microcontroller to sleep
cli(); // Disable interrupts
if(digitalRead(CAN0_INT)) // Make sure we havn't missed an interrupt between the digitalRead() above and now. If an interrupt happens between now and sei()/sleep_cpu() then sleep_cpu() will immediately wake up again
{
sleep_enable();
sleep_bod_disable();
sei();
sleep_cpu();
sleep_disable();
}
sei();

Serial.println(F("WAKE"));

CAN0.setMode(MCP_NORMAL); // When the MCP2515 wakes up it will be in LISTENONLY mode, here we put it into NORMAL mode
}

static void ISR_CAN()
{
// We don't do anything here, this is just for waking up the microcontroller
}

/*********************************************************************************************************
END FILE
*********************************************************************************************************/
122 changes: 89 additions & 33 deletions mcp_can.cpp
Expand Up @@ -37,7 +37,7 @@ void MCP_CAN::mcp2515_reset(void)
spi_readwrite(MCP_RESET);
MCP2515_UNSELECT();
SPI.endTransaction();
delayMicroseconds(10);
delay(5); // If the MCP2515 was in sleep mode when the reset command was issued then we need to wait a while for it to reset properly
}

/*********************************************************************************************************
Expand Down Expand Up @@ -144,6 +144,15 @@ INT8U MCP_CAN::mcp2515_readStatus(void)
return i;
}

/*********************************************************************************************************
** Function name: setSleepWakeup
** Descriptions: Enable or disable the wake up interrupt (If disabled the MCP2515 will not be woken up by CAN bus activity)
*********************************************************************************************************/
void MCP_CAN::setSleepWakeup(const INT8U enable)
{
mcp2515_modifyRegister(MCP_CANINTE, MCP_WAKIF, enable ? MCP_WAKIF : 0);
}

/*********************************************************************************************************
** Function name: setMode
** Descriptions: Sets control mode
Expand All @@ -160,17 +169,60 @@ INT8U MCP_CAN::setMode(const INT8U opMode)
*********************************************************************************************************/
INT8U MCP_CAN::mcp2515_setCANCTRL_Mode(const INT8U newmode)
{
INT8U i;

mcp2515_modifyRegister(MCP_CANCTRL, MODE_MASK, newmode);

i = mcp2515_readRegister(MCP_CANCTRL);
i &= MODE_MASK;

if ( i == newmode )
return MCP2515_OK;
// If the chip is asleep and we want to change mode then a manual wake needs to be done
// This is done by setting the wake up interrupt flag
// This undocumented trick was found at https://github.com/mkleemann/can/blob/master/can_sleep_mcp2515.c
if((mcp2515_readRegister(MCP_CANSTAT) & MODE_MASK) == MCP_SLEEP && newmode != MCP_SLEEP)
{
// Make sure wake interrupt is enabled
byte wakeIntEnabled = (mcp2515_readRegister(MCP_CANINTE) & MCP_WAKIF);
if(!wakeIntEnabled)
mcp2515_modifyRegister(MCP_CANINTE, MCP_WAKIF, MCP_WAKIF);

// Set wake flag (this does the actual waking up)
mcp2515_modifyRegister(MCP_CANINTF, MCP_WAKIF, MCP_WAKIF);

// Wait for the chip to exit SLEEP and enter LISTENONLY mode.

// If the chip is not connected to a CAN bus (or the bus has no other powered nodes) it will sometimes trigger the wake interrupt as soon
// as it's put to sleep, but it will stay in SLEEP mode instead of automatically switching to LISTENONLY mode.
// In this situation the mode needs to be manually set to LISTENONLY.

if(mcp2515_requestNewMode(MCP_LISTENONLY) != MCP2515_OK)
return MCP2515_FAIL;

// Turn wake interrupt back off if it was originally off
if(!wakeIntEnabled)
mcp2515_modifyRegister(MCP_CANINTE, MCP_WAKIF, 0);
}

// Clear wake flag
mcp2515_modifyRegister(MCP_CANINTF, MCP_WAKIF, 0);

return mcp2515_requestNewMode(newmode);
}

return MCP2515_FAIL;
/*********************************************************************************************************
** Function name: mcp2515_requestNewMode
** Descriptions: Set control mode
*********************************************************************************************************/
INT8U MCP_CAN::mcp2515_requestNewMode(const INT8U newmode)
{
byte startTime = millis();

// Spam new mode request and wait for the operation to complete
while(1)
{
// Request new mode
// This is inside the loop as sometimes requesting the new mode once doesn't work (usually when attempting to sleep)
mcp2515_modifyRegister(MCP_CANCTRL, MODE_MASK, newmode);

byte statReg = mcp2515_readRegister(MCP_CANSTAT);
if((statReg & MODE_MASK) == newmode) // We're now in the new mode
return MCP2515_OK;
else if((byte)(millis() - startTime) > 200) // Wait no more than 200ms for the operation to complete
return MCP2515_FAIL;
}
}

/*********************************************************************************************************
Expand Down Expand Up @@ -503,24 +555,24 @@ INT8U MCP_CAN::mcp2515_init(const INT8U canIDMode, const INT8U canSpeed, const I
if(res > 0)
{
#if DEBUG_MODE
Serial.print("Entering Configuration Mode Failure...\r\n");
Serial.println(F("Entering Configuration Mode Failure..."));
#endif
return res;
}
#if DEBUG_MODE
Serial.print("Entering Configuration Mode Successful!\r\n");
Serial.println(F("Entering Configuration Mode Successful!"));
#endif

// Set Baudrate
if(mcp2515_configRate(canSpeed, canClock))
{
#if DEBUG_MODE
Serial.print("Setting Baudrate Failure...\r\n");
Serial.println(F("Setting Baudrate Failure..."));
#endif
return res;
}
#if DEBUG_MODE
Serial.print("Setting Baudrate Successful!\r\n");
Serial.println(F("Setting Baudrate Successful!"));
#endif

if ( res == MCP2515_OK ) {
Expand Down Expand Up @@ -572,7 +624,7 @@ INT8U MCP_CAN::mcp2515_init(const INT8U canIDMode, const INT8U canSpeed, const I

default:
#if DEBUG_MODE
Serial.print("`Setting ID Mode Failure...\r\n");
Serial.println(F("`Setting ID Mode Failure..."));
#endif
return MCP2515_FAIL;
break;
Expand All @@ -583,7 +635,7 @@ INT8U MCP_CAN::mcp2515_init(const INT8U canIDMode, const INT8U canSpeed, const I
if(res)
{
#if DEBUG_MODE
Serial.print("Returning to Previous Mode Failure...\r\n");
Serial.println(F("Returning to Previous Mode Failure..."));
#endif
return res;
}
Expand Down Expand Up @@ -785,12 +837,12 @@ INT8U MCP_CAN::init_Mask(INT8U num, INT8U ext, INT32U ulData)
{
INT8U res = MCP2515_OK;
#if DEBUG_MODE
Serial.print("Starting to Set Mask!\r\n");
Serial.println(F("Starting to Set Mask!"));
#endif
res = mcp2515_setCANCTRL_Mode(MODE_CONFIG);
if(res > 0){
#if DEBUG_MODE
Serial.print("Entering Configuration Mode Failure...\r\n");
Serial.println(F("Entering Configuration Mode Failure..."));
#endif
return res;
}
Expand All @@ -807,12 +859,13 @@ INT8U MCP_CAN::init_Mask(INT8U num, INT8U ext, INT32U ulData)
res = mcp2515_setCANCTRL_Mode(mcpMode);
if(res > 0){
#if DEBUG_MODE
Serial.print("Entering Previous Mode Failure...\r\nSetting Mask Failure...\r\n");
Serial.println(F("Entering Previous Mode Failure..."));
Serial.println(F("Setting Mask Failure..."));
#endif
return res;
}
#if DEBUG_MODE
Serial.print("Setting Mask Successful!\r\n");
Serial.println(F("Setting Mask Successful!"));
#endif
return res;
}
Expand All @@ -826,12 +879,12 @@ INT8U MCP_CAN::init_Mask(INT8U num, INT32U ulData)
INT8U res = MCP2515_OK;
INT8U ext = 0;
#if DEBUG_MODE
Serial.print("Starting to Set Mask!\r\n");
Serial.println(F("Starting to Set Mask!"));
#endif
res = mcp2515_setCANCTRL_Mode(MODE_CONFIG);
if(res > 0){
#if DEBUG_MODE
Serial.print("Entering Configuration Mode Failure...\r\n");
Serial.println(F("Entering Configuration Mode Failure..."));
#endif
return res;
}
Expand All @@ -851,12 +904,13 @@ INT8U MCP_CAN::init_Mask(INT8U num, INT32U ulData)
res = mcp2515_setCANCTRL_Mode(mcpMode);
if(res > 0){
#if DEBUG_MODE
Serial.print("Entering Previous Mode Failure...\r\nSetting Mask Failure...\r\n");
Serial.println(F("Entering Previous Mode Failure..."));
Serial.println(F("Setting Mask Failure..."));
#endif
return res;
}
#if DEBUG_MODE
Serial.print("Setting Mask Successful!\r\n");
Serial.println(F("Setting Mask Successful!"));
#endif
return res;
}
Expand All @@ -869,13 +923,13 @@ INT8U MCP_CAN::init_Filt(INT8U num, INT8U ext, INT32U ulData)
{
INT8U res = MCP2515_OK;
#if DEBUG_MODE
Serial.print("Starting to Set Filter!\r\n");
Serial.println(F("Starting to Set Filter!"));
#endif
res = mcp2515_setCANCTRL_Mode(MODE_CONFIG);
if(res > 0)
{
#if DEBUG_MODE
Serial.print("Enter Configuration Mode Failure...\r\n");
Serial.println(F("Enter Configuration Mode Failure..."));
#endif
return res;
}
Expand Down Expand Up @@ -914,12 +968,13 @@ INT8U MCP_CAN::init_Filt(INT8U num, INT8U ext, INT32U ulData)
if(res > 0)
{
#if DEBUG_MODE
Serial.print("Entering Previous Mode Failure...\r\nSetting Filter Failure...\r\n");
Serial.println(F("Entering Previous Mode Failure..."));
Serial.println(F("Setting Filter Failure..."));
#endif
return res;
}
#if DEBUG_MODE
Serial.print("Setting Filter Successfull!\r\n");
Serial.println(F("Setting Filter Successfull!"));
#endif

return res;
Expand All @@ -935,13 +990,13 @@ INT8U MCP_CAN::init_Filt(INT8U num, INT32U ulData)
INT8U ext = 0;

#if DEBUG_MODE
Serial.print("Starting to Set Filter!\r\n");
Serial.println(F("Starting to Set Filter!"));
#endif
res = mcp2515_setCANCTRL_Mode(MODE_CONFIG);
if(res > 0)
{
#if DEBUG_MODE
Serial.print("Enter Configuration Mode Failure...\r\n");
Serial.println(F("Enter Configuration Mode Failure..."));
#endif
return res;
}
Expand Down Expand Up @@ -983,12 +1038,13 @@ INT8U MCP_CAN::init_Filt(INT8U num, INT32U ulData)
if(res > 0)
{
#if DEBUG_MODE
Serial.print("Entering Previous Mode Failure...\r\nSetting Filter Failure...\r\n");
Serial.println(F("Entering Previous Mode Failure..."));
Serial.println(F("Setting Filter Failure..."));
#endif
return res;
}
#if DEBUG_MODE
Serial.print("Setting Filter Successfull!\r\n");
Serial.println(F("Setting Filter Successfull!"));
#endif

return res;
Expand Down

0 comments on commit 43dc655

Please sign in to comment.