CANFDunino Timer Interrupt

Timers are very useful tools that provide accurate timing for embedded systems. Interrupts are also crucial in these systems as they allow for quick responses. A timer interrupt is a great combination because it ensures that important tasks are done on time without any delays. This is different from many slower systems, like some PC operating systems or certain interpreted scripting languages.versus compiled languages.

In CAN bus applications, a key use of a timer interrupt is to send messages regularly. In a CAN system, messages are usually sent on a regular schedule for things like control systems, displays, or collecting data. This is known as synchronous transmission, which means messages are sent at set times, regardless of other events. For example, an asynchronous message might say, “when I get a message from X, then send Y,” meaning the CAN node waits for an outside event to send something. In contrast, synchronous messages are sent at specific intervals, often just starting automatically.

Many CAN protocols, like CANOpen or “free-running CAN”, use a periodic transmission of messages, while OBD2 is more of an asynchronous protocol (but can still benefit from using timers to send requests to the vehicle).

Implementing a timer interrupt on the CANFDuino involves working directly with the SAMC21 processor. Fortunately, there is a lot of example code available for SAMx, along with useful information in the datasheets. Below is the timer code and a simple example for sending data. You can find this in the GitHub repo under the examples CANFDuino_TimerTx.ino. This example is easy to understand and can be used for protocols, data collection, or control systems.

SAMC21 TC3 Timer Interrupt Code

#ifndef SAMC21_TIMER_H
#define SAMC21_TIMER_H    

//dump to the screen
//#define DEBUG_PRINTRX

/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/
#include "typedef.h"
/*----------------------------------------------------------------------------
 *        Local definitions
 *----------------------------------------------------------------------------*/

// Enable TC3 interrupts
#define ENABLE_TIMER     NVIC_EnableIRQ(TC3_IRQn)
#define DISABLE_TIMER    NVIC_DisableIRQ(TC3_IRQn)
                    

// Static function pointer declaration
static void (*timerCallback)(void) = NULL;


bool setupTC3TimerInterrupt(UINT32 time_uS, void (*handler)(void))
{
    
    bool retVal = true;
    if(handler)
    {


        // Enable GCLK for TC3 (TC3 is connected to GCLK0 by default)
        GCLK->PCHCTRL[TC3_GCLK_ID].reg = (uint16_t) (GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK0);
        while (GCLK->PCHCTRL[TC3_GCLK_ID].bit.CHEN == 0);

        //disable counter
        TC3->COUNT16.CTRLA.reg &= !TC_CTRLA_ENABLE;

        // Reset TC3
        TC3->COUNT16.CTRLA.reg = TC_CTRLA_SWRST;
        while (TC3->COUNT16.SYNCBUSY.bit.SWRST);
        while (TC3->COUNT16.CTRLA.bit.SWRST);

        // Configure TC3 for recurring compare capture interrupt 
        //set timer to prescale by 256, (48MHz/265) = 187.5kHz. 1/187.5kHz =  5.3uS per tick
        TC3->COUNT16.CTRLA.reg = 0x00001610; 
        TC3->COUNT16.WAVE.reg = 1;
        TC3->COUNT16.CC[0].reg = (time_uS / 5.3) -1 ;   
        TC3->COUNT16.INTENSET.reg = TC_INTENSET_MC0; 
        while (TC3->COUNT16.SYNCBUSY.bit.CC0);

        // Enable TC3
        TC3->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;
        while (TC3->COUNT16.SYNCBUSY.bit.ENABLE);

        timerCallback = handler;
        NVIC_EnableIRQ(TC3_IRQn);

    }else
    {
        retVal = false;
    }
    return(retVal);
}


 void TC3_Handler() 
{
  //if (TC3->COUNT16.INTFLAG.bit.OVF) 
  if (TC3->COUNT16.INTFLAG.bit.MC0) 
  {
      if(timerCallback);
      {

          timerCallback();
      }
      
      TC3->COUNT16.INTFLAG.reg = TC_INTFLAG_MC0;
  }
}


#endif

CANFDuino Timer Based Transmission Code

cCAN_CANFD CanPort0(0, _500K, _500K, MCAN_MODE_CAN);
cCANTXFrame txMsgs[10];

BOOL bit,passFail;
UINT32 count, prevCount, ticks, uSecs, prevRxCtr0,prevRxCtr1;
UINT8 i, x;

void sendCAN()
{
      CanPort0.TxMsgs(ALL_MSGS);
}

// the setup function runs once when you press reset or power the board
void setup() 
    {
    // initialize digital pin LED_BUILTIN as an output.
    pinMode(28, OUTPUT);
        
    Serial.begin(115200);
    Serial.println("system reset");

    //set TX frame to 0x100
    for(i=0;i<10;i++)
    {
      txMsgs[i].id = 0x100 + i;
      txMsgs[i].len = 8;
      txMsgs[i].data[0] = i;
      CanPort0.addTxMessage(&txMsgs[i]);    
    }

    //start CAN hardware
    CanPort0.Initialize();
    CanPort0.setFiltRxAll();
    //setup timer and callback funciton for the timer
    setupTC3TimerInterrupt(100000, sendCAN);


  }

// the loop function runs over and over again forever
void loop() 
{
      //poll queue on RxMessages
        CanPort0.RxMsgs();

          for(i=0; i<CanPort0.numRxMsgs; i++)
          {
            Serial.print("CAN0 Rx Message on ID: 0x");
            Serial.println(CanPort0.rxMsgs[i].rxMsgInfo.id,HEX);
          }
}