Integrator Resources

The official home for NAI Support

Not sure where to start? Try Quick Start Guide or ask a question below!

Toggle Components with Visual Button
JavaScript Form Processing

CAN Interrupt Basic

CAN Interrupt Basic Sample Application (SSK 1.x)

Overview

The CAN Interrupt Basic sample application demonstrates how to configure and handle hardware interrupts on CAN (Controller Area Network) modules using the NAI Software Support Kit (SSK 1.x). CAN is an automotive and industrial serial bus standard designed for reliable, real-time communication between electronic control units. This sample configures CAN module interrupts to detect two types of events: message reception (a new CAN frame has arrived in the receive buffer) and system warning/toggle conditions (the bus error state has changed).

Unlike discrete or analog module interrupts that monitor voltage thresholds or signal transitions, CAN interrupts operate at the protocol level. They notify your application when the CAN controller has successfully captured a frame from the bus or when the node’s error management state has changed. Understanding these protocol-level events is essential for building reliable CAN applications that can react to incoming data and detect bus health degradation before it becomes critical.

This sample supports the following CAN module types: CB1 (CAN A/B), CB2 (J1939), CB3 (J1939 with per-channel protocol selection), and CB6 (J1939 with per-channel protocol selection and physical line break detection).

For background on interrupt concepts — including edge vs. level triggering, interrupt vector numbering, steering architecture, and latency measurement — see the Interrupts API Guide. This guide focuses on how those concepts apply specifically to CAN modules and walks through the practical implementation using the SSK 1.x API.

Prerequisites

Before running this sample, make sure you have:

  • An NAI board with a CAN module installed (CB1, CB2, CB3, or CB6).

  • SSK 1.x installed on your development host.

  • The sample applications built. Refer to the SSK 1.x build instructions for your platform if you have not already compiled them.

  • A CAN bus with at least two active nodes and proper 120-ohm termination at each end of the bus.

How to Run

Launch the CAN_Interrupt_Basic executable from your build output directory. On startup the application looks for a configuration file (default_CAN_Interrupt_Basic.txt). On the first run, this file will not exist — the application will present an interactive board menu where you configure a board connection, card index, and module slot. You can save this configuration so that subsequent runs skip the menu and connect automatically. Once connected, the application prompts you for a channel number and interrupt steering mode, configures CAN Rx interrupts on the selected channel, and begins waiting for incoming CAN frames.

Board Connection and Module Selection

Note
This startup sequence is common to all NAI sample applications. The board connection and module selection code shown here is not specific to CAN. For details on board connection configuration, see the First Time Setup Guide.

The main() function (named CAN_Interrupt_Basic() on VxWorks) follows a standard SSK 1.x startup flow:

  1. Initialize default CAN configuration with initializeCANConfigurations() and default interrupt configuration with initializeInterruptConfigurations().

  2. Call naiapp_RunBoardMenu() to load a saved configuration file or present the interactive board menu. The configuration file (default_CAN_Interrupt_Basic.txt) is not included with the SSK — it is created when you save your connection settings from the board menu. On the first run, the menu always appears.

  3. Query for card index and module number.

  4. Retrieve the module ID with naibrd_GetModuleID() to identify the installed CAN module type, then proceed to interrupt configuration.

#if defined (__VXWORKS__)
int32_t CAN_Interrupt_Basic(void)
#else
int32_t main(void)
#endif
{
   bool_t stop = FALSE;
   int32_t cardIndex;
   int32_t moduleCnt;
   int32_t module;
   int8_t inputBuffer[80];
   int32_t inputResponseCnt;

   initializeCANConfigurations(0, 0, 0, 0, 0, 0, NAI_J1939_MAX_DATA_LEN, 1, NAI_CAN_500K_BAUD);
   initializeInterruptConfigurations(FALSE, FALSE, FALSE, 0, 0, 0, 0);

   if (naiapp_RunBoardMenu(CONFIG_FILE) == TRUE)
   {
      while (stop != TRUE)
      {
         stop = naiapp_query_CardIndex(naiapp_GetBoardCnt(), 0, &cardIndex);
         inputCANConfig.cardIndex = cardIndex;
         if (stop != TRUE)
         {
            check_status(naibrd_GetModuleCount(cardIndex, &moduleCnt));
            stop = naiapp_query_ModuleNumber(moduleCnt, 1, &module);
            inputCANConfig.module = module;
            if (stop != TRUE)
            {
               inputCANConfig.modid = naibrd_GetModuleID(cardIndex, module);
               if ((inputCANConfig.modid != 0))
               {
                  Run_CAN_Interrupt_Basic();
               }
            }
         }
         printf("\nType Q to quit or Enter key to restart application:\n");
         stop = naiapp_query_ForQuitResponse(sizeof(inputBuffer), NAI_QUIT_CHAR, inputBuffer, &inputResponseCnt);
      }
   }

   printf("\nType the Enter key to exit the program: ");
   naiapp_query_ForQuitResponse(sizeof(inputBuffer), NAI_QUIT_CHAR, inputBuffer, &inputResponseCnt);
   naiapp_access_CloseAllOpenCards();

   return 0;
}
Important

Common connection errors you may encounter at this stage:

  • No board found — verify that the board is powered on and physically connected. Check that the configuration file lists the correct interface and address.

  • Connection timeout — confirm network settings (for Ethernet connections) or bus configuration (for PCI/PCIe). Firewalls and IP mismatches are frequent causes.

  • Invalid card or module index — indices are zero-based for cards and one-based for modules. Ensure the values you pass match your hardware setup.

  • Module not present at selected slot — the slot you selected does not contain a CAN module. Use the board menu to verify which slots are populated.

Program Structure

The CAN Interrupt Basic sample is split across two source files:

  • CAN_Interrupt_Basic.c — the main entry point. Handles board connection, user queries for channel and steering, and orchestrates the interrupt lifecycle from installation through cleanup.

  • nai_can_int.c — shared CAN interrupt utility functions. Contains the interrupt configuration logic (configureCANToInterruptOnRx()), the ISR (basic_ISR_CAN()), the interrupt polling loop (CAN_checkForInterrupt()), and the interrupt enable/disable logic (enableCANInterrupts()).

The application also uses nai_can_cfg.h and its implementation for CAN configuration structures and Rx setup. The CanConfig structure tracks card index, module number, module ID, channel range, payload size, and baud rate. The InterruptConfig structure tracks steering mode, IRQ, edge/level trigger setting, and interrupt clearing preferences.

After the board connection is established, Run_CAN_Interrupt_Basic() follows a linear flow:

  1. Query the user for the Rx channel number.

  2. Query the user for the interrupt steering mode (onboard, offboard PCI, offboard PCIe).

  3. Install the ISR with naibrd_InstallISR().

  4. Configure the CAN module for Rx interrupts (disable, clear, set vector, set edge/level, set steering).

  5. Enable interrupts and configure the channel for Rx (set baud rate, enable Rx).

  6. Enter a polling loop — the user presses Enter to check whether an interrupt has occurred.

  7. On exit, disable Rx on the channel and uninstall the ISR.

static bool_t Run_CAN_Interrupt_Basic()
{
    bool_t bQuit = FALSE;
    int32_t maxChannel = naibrd_CAN_GetChannelCount(inputCANConfig.modid);
    int32_t minChannel = 1;

   /* Query user for RX channel */
   if(!bQuit)
   {
      bQuit = naiapp_query_ChannelNumber(maxChannel,minChannel,&inputCANConfig.channel);
      inputCANConfig.minChannel = inputCANConfig.channel;
      inputCANConfig.maxChannel = inputCANConfig.channel;
   }
   /* Query user for location interrupt will be sent out to */
   if(!bQuit)
   {
      bQuit = GetIntSteeringTypeFromUser(&inputInterruptConfig.steering);
   }

   if (!bQuit)
   {
      /**** 2. Implement Bus Interrupt Handling****/
      setIRQ(inputInterruptConfig.steering,&inputInterruptConfig.irq);
      naibrd_InstallISR(inputCANConfig.cardIndex,inputInterruptConfig.irq, basic_ISR_CAN,NULL);

      /****3. configure Module to perform interrupts****/
      configureCANToInterruptOnRx(inputInterruptConfig,inputCANConfig);
      enableCANInterrupts(inputCANConfig,TRUE);

      /****4. Configure Module to cause Interrupts ****/
      Cfg_Rx_CAN(inputCANConfig);

      /****5. Show  Interrupt Handling (contains step 6)****/
      bQuit = CAN_checkForInterrupt(inputCANConfig);

      /*****7. Clear Module Configurations*****/
      check_status(naibrd_CAN_SetRxEnable(inputCANConfig.cardIndex, inputCANConfig.module, inputCANConfig.channel, FALSE));

      /*****8. Clear Board Configurations *****/
      check_status(naibrd_UninstallISR(inputCANConfig.cardIndex));
   }
   return bQuit;
}

The menu system is a sample convenience — in your own code, call these API functions directly. The key sequence is: install ISR, configure interrupts, enable Rx, wait for interrupts, then clean up.

CAN Interrupt Status Types

The CAN module exposes two distinct interrupt status types. Each type monitors a different protocol-level condition and latches when that condition is detected. Understanding what each type reports — and the CAN bus error management model behind them — will help you decide which ones to enable in your application.

Receive Message (RECV_MSG)

NAI_CAN_STATUS_RECV_MSG_LATCHED

A new CAN frame has been received and placed in the channel’s Rx FIFO buffer. This is the primary data-availability interrupt for CAN. It fires when the CAN controller hardware successfully captures a frame from the bus that matches the channel’s acceptance filter. The corresponding bit in the status register indicates which channel(s) received data.

Enable RECV_MSG interrupts for any application that needs to process incoming CAN frames with low latency. Without this interrupt, your application must poll the Rx FIFO periodically, which wastes CPU time when traffic is sparse and may miss frames when traffic is bursty. With RECV_MSG enabled, your ISR is notified the moment a frame arrives, allowing you to read and process it immediately.

When the interrupt fires, read the Rx FIFO with the CAN FIFO API functions to retrieve the received frame data, then clear the latched status to re-arm the interrupt for the next frame.

System Warning / Toggle (SWT)

NAI_CAN_STATUS_SWT_LATCHED

The CAN node’s bus error state has changed. This interrupt monitors transitions between the CAN protocol’s error severity levels and fires when the node moves from one error state to another.

To understand SWT, you need to understand how CAN bus error management works. The CAN protocol uses two error counters maintained by every node on the bus: a Transmit Error Counter (TEC) and a Receive Error Counter (REC). Every time a node detects a bit error, a CRC mismatch, a stuffing violation, or other bus fault, the relevant counter increments. Successful transmissions and receptions decrement the counters. Based on these counter values, the CAN specification defines four error states with escalating severity:

  • Error Active (TEC < 96 and REC < 96) — the node is operating normally. It can transmit and receive freely and will send active error flags when it detects a bus fault. This is the healthy state.

  • Error Warning (TEC >= 96 or REC >= 96) — the node is still fully functional but is experiencing a significant number of bus errors. This is an early warning that something is degrading — typically a wiring issue, a noisy environment, a misconfigured baud rate on another node, or a failing transceiver.

  • Error Passive (TEC >= 128 or REC >= 128) — the node can still transmit and receive, but it must now send passive error flags instead of active ones, and it must wait an additional delay after each transmission. Communication continues but at reduced effectiveness. The node is becoming increasingly isolated from the bus.

  • Bus Off (TEC >= 256) — the node has disconnected itself from the bus entirely. It cannot transmit or receive. Recovery requires the node to detect 128 occurrences of 11 consecutive recessive bits, which typically takes several milliseconds. Bus Off is the most severe state and usually indicates a major hardware fault, a cable break, or a sustained protocol mismatch.

The SWT interrupt fires when the node transitions between these states — for example, from Error Active to Error Warning, or from Error Warning to Error Passive. Each transition indicates a meaningful change in bus health. By enabling SWT interrupts, your application can react to bus degradation in real time: logging warnings, alerting operators, initiating failover logic, or gracefully shutting down CAN communication before a Bus Off condition causes uncontrolled message loss.

When SWT fires, call naibrd_CAN_GetSWTData() to retrieve the fault type and fault value. The fault types include wire disconnect (NAI_CAN_WIRE_DISCONNECT), wire open (NAI_CAN_WIRE_OPEN), and main bus short (NAI_CAN_MAIN_BUS_SHORT). The fault value provides the associated error counter reading.

This sample enables RECV_MSG interrupts for demonstration. In your own application, you may want to enable both RECV_MSG and SWT to get complete visibility into both data reception and bus health. Consult the CB1-3/CB8 module manual for detailed information on error counter behavior and SWT fault classifications.

Interrupt Configuration

The configureCANToInterruptOnRx() function in nai_can_int.c sets up the CAN module to generate interrupts when messages are received. The configuration follows a five-step sequence: disable existing interrupts, clear stale status, set the interrupt vector, configure the trigger mode, and set interrupt steering.

Step 1: Disable Interrupts and Clear Status

Always disable interrupts before changing configuration to avoid spurious interrupts during setup. The function begins by writing 0 to the interrupt enable register to disable all channel interrupts, then clears the RECV_MSG latched status register by writing 0xFFFF to clear any stale bits from a previous run.

/* clear interruptConfigs of previous channels */
check_status(naibrd_CAN_SetIntEnableRaw(cardIndex, module, 0));
check_status(naibrd_CAN_ClearStatusRaw(cardIndex, module, type, 0xFFFF));

If stale latched status bits remain from a previous run, they will not generate a new edge when interrupts are re-enabled, and the interrupt controller will never fire. Always clear status before enabling.

Step 2: Set Interrupt Vector

The vector identifies which interrupt source generated the event when the ISR fires. The sample assigns NAI_CAN_INTERRUPT_VECTOR (0x80) to the RECV_MSG status type. The 1 in the channel parameter sets the vector for channel 1 — the CAN API uses this channel parameter to associate the vector with the status type.

check_status(naibrd_CAN_SetIntVector(cardIndex, module, 1, type, vector));

In your own application, if you have multiple interrupt sources on the same board, assign distinct vectors so your ISR can identify the source without reading status registers.

Step 3: Set Trigger Mode

The edge/level trigger mode determines how the interrupt fires. Edge-triggered mode (value 0) fires once when the condition is first detected — the interrupt must be cleared and re-armed to fire again. Level-triggered mode (value 1) continues to assert as long as the condition remains active. For CAN RECV_MSG interrupts, edge triggering is the typical choice because you want a single notification per received frame.

check_status(naibrd_CAN_SetInterruptEdgeLevel(cardIndex, module, type, interrupt_Edge_Trigger));

For detailed theory on edge vs. level triggering and when to use each mode, see the Interrupts API Guide.

Step 4: Set Interrupt Steering

Interrupt steering determines how the interrupt signal is routed from the CAN module to your application. The sample passes the user’s steering selection to naibrd_CAN_SetInterruptSteering().

check_status(naibrd_CAN_SetInterruptSteering(cardIndex, module, type, steering));
Note
CAN uses module-level interrupt steering with naibrd_CAN_SetInterruptSteering(), which applies the steering setting to the entire module for a given status type. This differs from per-channel modules like DT, where steering is set per status type per channel group. For CAN, a single call configures steering for all channels on the module for the specified status type.

The steering options are the same as for other module types:

  • Onboard (NAIBRD_INT_STEERING_ON_BOARD_0) — handled locally on the board’s processor.

  • cPCI offboard (NAIBRD_INT_STEERING_CPCI_APP) — routes the interrupt to the CompactPCI backplane host.

  • PCIe offboard (NAIBRD_INT_STEERING_PCIE_APP) — routes the interrupt to the PCIe host.

Make sure the steering mode you select matches the IRQ ID used in naibrd_InstallISR() — a mismatch will silently prevent interrupts from reaching your ISR.

Step 5: Enable Rx and Interrupts

After configuring the interrupt path, two additional steps complete the setup. First, enableCANInterrupts() writes a bitmask to the interrupt enable register with the selected channel’s bit set:

void enableCANInterrupts(CanConfig inputCANConfig, bool_t enable) {
   int32_t channel;
   int32_t enableRegVal = 0;
   int32_t isEnabled = 0;
   for (channel = inputCANConfig.minChannel; channel <= inputCANConfig.maxChannel; channel++)
   {
      isEnabled = 1 << (channel - 1);
      if (enable)
         enableRegVal = (1 << (channel - 1)) | enableRegVal;
      else if (isEnabled  & enableRegVal)
         enableRegVal = (1 << (channel - 1)) ^ enableRegVal;
   }
   naibrd_CAN_SetIntEnableRaw(inputCANConfig.cardIndex, inputCANConfig.module, enableRegVal);
}

Then Cfg_Rx_CAN() sets the baud rate timing parameters and enables Rx on the channel with naibrd_CAN_SetRxEnable(). The channel will not receive any CAN frames until Rx is explicitly enabled — this is a critical step that is easy to overlook.

Important

Common interrupt configuration errors:

  • Rx not enabled on the channel — the CAN controller will not capture any frames until naibrd_CAN_SetRxEnable() is called with TRUE. If RECV_MSG interrupts never fire despite correct interrupt configuration, this is the most likely cause.

  • ISR installation failure — naibrd_InstallISR() returns an error when the steering mode does not match your hardware configuration or bus type. Verify that your steering selection matches the physical bus connection.

  • Interrupts not firing after enable — if latched status registers are not cleared before enabling interrupts, stale status bits from a previous run will not generate a new edge and the interrupt controller will never fire. Always clear status before enabling.

  • Wrong steering mode — selecting onboard steering when the ISR is installed as offboard (or vice versa) results in interrupts being routed to a path with no handler.

Interrupt Handler

The CAN Interrupt Basic sample uses a simple ISR and polling model. The ISR (basic_ISR_CAN()) runs in interrupt context and sets a flag and captures the vector. The polling function (CAN_checkForInterrupt()) runs in the main thread and checks whether the ISR has fired.

ISR: basic_ISR_CAN()

The ISR is minimal by design — it sets the interruptOccured flag and captures the interrupt vector, then returns immediately. Keeping the ISR short is critical because it runs in interrupt context where blocking operations and lengthy processing are not allowed.

#if defined (__VXWORKS__)
void basic_ISR_CAN(uint32_t param)
#else
void basic_ISR_CAN(void *param, uint32_t vector)
#endif
{
   interruptOccured = TRUE;

#if defined (__VXWORKS__)
   interruptVector = nai_Onboard_GetInterruptVector();
   nai_Onboard_ClearInterrupt();
#elif defined (WIN32)
   UNREFERENCED_PARAMETER(param);
   interruptVector = vector;
#else
   interruptVector = vector;
#endif
}

On VxWorks, the ISR must read the vector from the board and clear the board-level interrupt within the ISR itself using nai_Onboard_GetInterruptVector() and nai_Onboard_ClearInterrupt(). On Windows and Linux, the vector is passed as a parameter by the driver.

Polling Loop: CAN_checkForInterrupt()

After the ISR is installed and interrupts are enabled, the application enters CAN_checkForInterrupt(). This function loops, waiting for the user to press Enter to check whether an interrupt has occurred. When the interruptOccured flag is set, it reads and displays the RECV_MSG latched status and the interrupt vector.

bool_t CAN_checkForInterrupt(CanConfig inputCANConfig) {
   bool_t bQuit;
   uint8_t status;
   int8_t inputBuffer[80];
   int32_t inputResponseCnt;

   status = 0;
   bQuit = FALSE;
   interruptOccured = FALSE;
   while (!bQuit) {
      printf("\nPress enter to check if interrupt Occurred (press Q to quit):");
      bQuit = naiapp_query_ForQuitResponse(sizeof(inputBuffer), NAI_QUIT_CHAR, inputBuffer, &inputResponseCnt);
      if (!bQuit) {
         if (interruptOccured) {
            naibrd_CAN_GetStatusRaw(inputCANConfig.cardIndex, inputCANConfig.module, NAI_CAN_STATUS_RECV_MSG_LATCHED, &status);
            printf("\nVector = %#x \n", interruptVector);
            printf("Status = %#x \n", status);
            interruptOccured = FALSE;
         }
         else {
            printf("\nNo Interrupt Occurred");
         }
         printf("\n\nWould you like to clear the status register? (default:N):");
         bQuit = naiapp_query_ForQuitResponse(sizeof(inputBuffer), NAI_QUIT_CHAR, inputBuffer, &inputResponseCnt);
         if (inputBuffer[0] == 'y' || inputBuffer[0] == 'Y')
         {
            naibrd_CAN_ClearStatusRaw(inputCANConfig.cardIndex, inputCANConfig.module, NAI_CAN_STATUS_RECV_MSG_LATCHED, status);
         }
      }
   }
   return bQuit;
}

The key points in this polling loop:

  1. Read status — naibrd_CAN_GetStatusRaw() reads the RECV_MSG latched status register. Each bit in the returned value corresponds to a channel (bit 0 = channel 1, bit 1 = channel 2, etc.). A set bit means that channel received a CAN frame.

  2. Display results — the vector and status are printed so you can verify which interrupt source fired and which channel received data.

  3. Clear status (re-arm) — naibrd_CAN_ClearStatusRaw() writes the status value back to clear the latched bits. For edge-triggered interrupts, this step is required to allow the next interrupt to fire. The sample makes clearing optional (user must type "Y") so you can observe the latched status persisting across multiple checks.

In your own application, you would not use a manual polling loop. Instead, your ISR or interrupt handler thread would read the Rx FIFO immediately when the interrupt fires, process the CAN frame data, and clear the status to re-arm automatically. The handleCANInterrupt() function in nai_can_int.c shows a more complete handler pattern that reads the FIFO data from all interrupted channels.

Cleanup

When the user quits the polling loop, the application performs two cleanup steps:

/* Clear Module Configurations */
check_status(naibrd_CAN_SetRxEnable(inputCANConfig.cardIndex, inputCANConfig.module, inputCANConfig.channel, FALSE));

/* Clear Board Configurations */
check_status(naibrd_UninstallISR(inputCANConfig.cardIndex));

Disabling Rx on the channel stops the CAN controller from receiving frames, and uninstalling the ISR removes the interrupt handler from the system. Always uninstall the ISR before your application exits to prevent stale function pointers from causing crashes if an interrupt fires after your handler code has been unloaded.

Troubleshooting Reference

This table summarizes common errors and symptoms covered in the sections above. For detailed context, refer to the relevant section. Consult your module’s manual for hardware-specific diagnostic procedures.

Debugging CAN Interrupts That Are Not Firing

For CAN interrupts, RECV_MSG not firing usually means Rx is not enabled on the channel, no CAN traffic is present on the bus, or the acceptance filter is rejecting all frames. Use this two-step approach to isolate the problem:

  1. Check whether the status registers are changing. Call naibrd_CAN_GetStatusRaw() to read the RECV_MSG latched status. If the bits are changing when CAN traffic is sent, the module is detecting frames correctly — the issue is in your interrupt delivery path (steering, ISR installation, or enable register).

  2. If the status registers are NOT changing, the issue is at the CAN bus or channel configuration level. Verify that Rx is enabled on the channel, the baud rate matches the bus, at least one other node is transmitting on the bus, and the acceptance filter is not rejecting the frames. You can also read the realtime status (NAI_CAN_STATUS_RECV_MSG_REALTIME) to see instantaneous state. Consult your module’s manual for the specific register addresses.

SWT interrupts firing repeatedly indicates ongoing bus errors — check cable termination, verify that at least two active CAN nodes are on the bus, and confirm that all nodes are using the same baud rate.

Error / Symptom Possible Causes Suggested Resolution

No board found or connection timeout

Board not powered, incorrect or missing configuration file, network issue

Verify hardware is powered and connected. If the configuration file exists, check that it lists the correct interface and address. If it does not exist, the board menu will appear — configure and save your connection settings.

Module not detected or not recognized as CAN

No CAN module installed at the selected slot, incorrect module number, or a non-CAN module is present

Verify hardware configuration. naibrd_CAN_GetChannelCount() returning 0 indicates the selected module is not a CAN type.

RECV_MSG interrupt never fires

Rx not enabled on the channel, no CAN traffic on the bus, acceptance filter rejecting all frames, status not cleared before enabling, steering mismatch

Verify naibrd_CAN_SetRxEnable() was called with TRUE. Confirm another node is transmitting CAN frames. Check that acceptance filter configuration allows the expected message IDs through. Follow the full setup sequence: disable, clear, configure, enable.

No CAN traffic on the bus

No other CAN node is transmitting, cable disconnected, baud rate mismatch between nodes

CAN requires at least two active nodes for successful communication. Verify physical connections and that all nodes use the same baud rate. Use an oscilloscope or CAN bus analyzer to verify bus activity.

Acceptance filter rejecting frames

The channel’s acceptance filter is configured to pass only specific message IDs that do not match the incoming traffic

Check the acceptance filter settings with the CAN filter API functions. For initial testing, configure the filter to accept all message IDs.

SWT interrupt fires repeatedly

Ongoing bus errors due to cable problems, missing termination, baud rate mismatch, or only one node on the bus

CAN buses require 120-ohm termination resistors at each end of the bus. Verify that at least two active nodes are present. Check that all nodes use the same baud rate. Use naibrd_CAN_GetSWTData() to read the specific fault type and error counter value.

BIT status errors

Internal self-test detected a channel anomaly, possible hardware fault or transceiver failure

Read the BIT status register to identify which channel is affected. Consult the module manual for BIT fault classifications and recommended actions.

Steering mismatch — ISR never called

Onboard steering selected but ISR installed as offboard (or vice versa), incorrect IRQ ID

Match the steering mode to where your application executes. Verify the IRQ ID passed to naibrd_InstallISR() corresponds to the steering mode configured with naibrd_CAN_SetInterruptSteering().

Interrupts stop after first event (edge-triggered)

Status register not cleared after handling — edge-triggered interrupts require clearing to re-arm

Call naibrd_CAN_ClearStatusRaw() in your interrupt handler for each status type that fired. The sample makes clearing optional for demonstration; in production code, always clear immediately.

Full Source

The complete source for this sample is provided below for reference. The sections above explain each part in detail. This sample consists of two source files: the main application and shared CAN interrupt utility functions.

Full Source — CAN_Interrupt_Basic.c (SSK 1.x)
/**************************************************************************************************************/
/**
<summary>

The CAN_Interrupt_Basic program demonstrates how to perform an interrupt when a single channel receives
a can message. The purpose of this program is to demonstrate the method calls in the naibrd library for performing
the interrupt. More information on this process can be found in the naibrd SSK Quick Guide(Interrupts) file.

</summary>
*/
/**************************************************************************************************************/

/************************/
/* Include Declarations */
/************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/*Common Module Specific Sample Program include files*/
#include "nai_can_int.h"
#include "nai_can_cfg.h"

/* Common Sample Program include files */
#include "include/naiapp_interrupt.h"
#include "include/naiapp_interrupt_ether.h"
#include "include/naiapp_boardaccess_menu.h"
#include "include/naiapp_boardaccess_query.h"
#include "include/naiapp_boardaccess_access.h"
#include "include/naiapp_boardaccess_display.h"
#include "include/naiapp_boardaccess_utils.h"

/* naibrd include files */
#include "nai.h"
#include "naibrd.h"

/* Module Specific NAI Board Library files */
#include "functions/naibrd_can.h"

/* Extern Functions or Variables*/
extern CanConfig inputCANConfig;
extern InterruptConfig inputInterruptConfig;

/********************/
/* Application Name */
/********************/

static const int8_t *CONFIG_FILE = (int8_t *)"default_CAN_Interrupt_Basic.txt";

/********************************/
/* Internal Function Prototypes */
/********************************/
static bool_t Run_CAN_Interrupt_Basic();

/**************************************************************************************************************/
/*****                                     Main Routine                                                   *****/
/**************************************************************************************************************/
/**************************************************************************************************************/
/**
<summary>

The main routine assists in gaining access to the board.

The following routines from the nai_sys_cfg.c file are
called to assist with accessing and configuring the board.

 - ConfigDevice
 - DisplayDeviceCfg
 - GetBoardSNModCfg
 - CheckModule

</summary>
*/
/*****************************************************************************/
#if defined (__VXWORKS__)
int32_t CAN_Interrupt_Basic(void)
#else
int32_t main(void)
#endif
{
   bool_t stop = FALSE;
   int32_t cardIndex;
   int32_t moduleCnt;
   int32_t module;
   int8_t inputBuffer[80];
   int32_t inputResponseCnt;

   initializeCANConfigurations(0, 0, 0, 0, 0, 0, NAI_J1939_MAX_DATA_LEN, 1, NAI_CAN_500K_BAUD);
   initializeInterruptConfigurations(FALSE, FALSE, FALSE, 0, 0, 0, 0);

   if (naiapp_RunBoardMenu(CONFIG_FILE) == TRUE)
   {
      while (stop != TRUE)
      {
         /* Query the user for the card index */
         stop = naiapp_query_CardIndex(naiapp_GetBoardCnt(), 0, &cardIndex);
         inputCANConfig.cardIndex = cardIndex;
         if (stop != TRUE)
         {
            check_status(naibrd_GetModuleCount(cardIndex, &moduleCnt));

            /* Query the user for the module number */
            stop = naiapp_query_ModuleNumber(moduleCnt, 1, &module);
            inputCANConfig.module = module;
            if (stop != TRUE)
            {
               inputCANConfig.modid = naibrd_GetModuleID(cardIndex, module);
               if ((inputCANConfig.modid != 0))
               {
                  Run_CAN_Interrupt_Basic();
               }
            }
         }
         printf("\nType Q to quit or Enter key to restart application:\n");
         stop = naiapp_query_ForQuitResponse(sizeof(inputBuffer), NAI_QUIT_CHAR, inputBuffer, &inputResponseCnt);
      }
   }

   printf("\nType the Enter key to exit the program: ");
   naiapp_query_ForQuitResponse(sizeof(inputBuffer), NAI_QUIT_CHAR, inputBuffer, &inputResponseCnt);
   naiapp_access_CloseAllOpenCards();

   return 0;
}

/**************************************************************************************************************/
/**
<summary>

This function is broken into the following major steps. These steps correspond with the steps provided
in the naibrd SSK Quick Guide(Interrupts) file.

2. Bus Interrupt Handling - Install ISR

   API CALLS - naibrd_InstallISR

3. Enable Module Interrupts- Configures module to interrupt when channel receives CAN message.

   API CALLS - naibrd_CAN_SetInterruptEdgeLevel, naibrd_CAN_SetIntVector, naibrd_CAN_SetInterruptSteering, naibrd_CAN_SetIntEnable

4. Configure Module to Cause Rx Interrupt - sets the BAUD Rate to 1mbit/s (CAN AB) or 500k bits/s (CAN J1939) as definted in nai_can_cfg.h
   It also enables the particular channel on the module to receive can messages

   API CALLS - naibrd_CAN_SetBitTiming , naibrd_CAN_SetRxEnable

5. Show Interrupt Handling - Check if any interrupt has occurred and report back the status and vector

6. Re-arming Interrupts - Clear the status register to allow interrupts to occur again. This is done by writing to the status register.
   In this program, we use an API call to do this.

   API CALLS - naibrd_CAN_ClearStatusRaw

7. Clear Module Configurations

   API CALLS - naibrd_CAN_SetRxEnable

8. Clear Board Configurations

   API CALLS - naibrd_UninstallISR

</summary>
*/
/**************************************************************************************************************/
static bool_t Run_CAN_Interrupt_Basic()
{
    bool_t bQuit = FALSE;
    int32_t maxChannel = naibrd_CAN_GetChannelCount(inputCANConfig.modid);
    int32_t minChannel = 1;
   /* Query user for RX channel */

   if(!bQuit)
   {

      bQuit = naiapp_query_ChannelNumber(maxChannel,minChannel,&inputCANConfig.channel);
      inputCANConfig.minChannel = inputCANConfig.channel;
      inputCANConfig.maxChannel = inputCANConfig.channel;
   }
   /* Query user for location interrupt will be sent out to */
   if(!bQuit)
   {
      bQuit = GetIntSteeringTypeFromUser(&inputInterruptConfig.steering);
   }

   if (!bQuit)
   {
      /**** 2. Implement Bus Interrupt Handling****/
      setIRQ(inputInterruptConfig.steering,&inputInterruptConfig.irq);
      naibrd_InstallISR(inputCANConfig.cardIndex,inputInterruptConfig.irq, basic_ISR_CAN,NULL);

      /****3. configure Module to perform interrupts****/
      configureCANToInterruptOnRx(inputInterruptConfig,inputCANConfig);
      enableCANInterrupts(inputCANConfig,TRUE);

      /****4. Configure Module to cause Interrupts ****/
      Cfg_Rx_CAN(inputCANConfig);

      /****5. Show  Interrupt Handling (contains step 6)****/
      bQuit = CAN_checkForInterrupt(inputCANConfig);

      /*****7. Clear Module Configurations*****/
      check_status(naibrd_CAN_SetRxEnable(inputCANConfig.cardIndex, inputCANConfig.module, inputCANConfig.channel, FALSE));

      /*****8. Clear Board Configurations *****/
      check_status(naibrd_UninstallISR(inputCANConfig.cardIndex));
   }
   return bQuit;
}
Full Source — nai_can_int.c (SSK 1.x)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Common Sample Program include files */
#include "include/naiapp_interrupt.h"
#include "include/naiapp_interrupt_ether.h"
#include "include/naiapp_boardaccess_menu.h"
#include "include/naiapp_boardaccess_query.h"
#include "include/naiapp_boardaccess_access.h"
#include "include/naiapp_boardaccess_display.h"
#include "include/naiapp_boardaccess_utils.h"

/* Common CAN Sample Program include files */
#include "nai_can_int.h"
#include "nai_can_cfg.h"

/* naibrd include files */
#include "nai.h"
#include "naibrd.h"
#include "naibrd_ether.h"
#include "functions/naibrd_can.h"
#include "maps/nai_map_can.h"

bool_t interruptOccured; /* used by CAN_checkForInterrupt to see if interrupt occurred */
int32_t frameCount;	/* counter increment when frames are found */
uint32_t interruptVector; /* used by CAN_checkForInterrupt to get vector of the interrupt that occurred in myIsr */
InterruptConfig inputInterruptConfig;

/* Extern Functions or Variables*/
extern CanConfig inputCANConfig;
extern FILE* fifoDataFile;

/***********************/
/* Function Prototypes */
/***********************/

void getFIFODataFromInterruptChannels(uint8_t status, FIFO* fifoData[NAI_GEN5_CAN_MAX_CHANNEL_COUNT], int32_t minChannel, int32_t maxChannel);

/**************************************************************************************************************/
/**
<summary>
Prompts user to check if an interrupt has occurred. MyIsr() should be installed if using this function.
</summary>
*/
/**************************************************************************************************************/
bool_t CAN_checkForInterrupt(CanConfig inputCANConfig) {
   bool_t bQuit;
   uint8_t status;
   int8_t inputBuffer[80];
   int32_t inputResponseCnt;


   status = 0;
   bQuit = FALSE;
   interruptOccured = FALSE;
   while (!bQuit) {
      printf("\nPress enter to check if interrupt Occurred (press Q to quit):");
      bQuit = naiapp_query_ForQuitResponse(sizeof(inputBuffer), NAI_QUIT_CHAR, inputBuffer, &inputResponseCnt);
      if (!bQuit) {
         if (interruptOccured) {

            naibrd_CAN_GetStatusRaw(inputCANConfig.cardIndex, inputCANConfig.module, NAI_CAN_STATUS_RECV_MSG_LATCHED, &status);

            printf("\nVector = %#x \n", interruptVector);
            printf("Status = %#x \n", status);
            interruptOccured = FALSE;
         }
         else {
            printf("\nNo Interrupt Occurred");
         }
         printf("\n\nWould you like to clear the status register? (default:N):");

         bQuit = naiapp_query_ForQuitResponse(sizeof(inputBuffer), NAI_QUIT_CHAR, inputBuffer, &inputResponseCnt);
         if (inputBuffer[0] == 'y' || inputBuffer[0] == 'Y')
         {
            naibrd_CAN_ClearStatusRaw(inputCANConfig.cardIndex, inputCANConfig.module, NAI_CAN_STATUS_RECV_MSG_LATCHED, status);
         }
      }
   }
   return bQuit;
}

/**************************************************************************************************************/
/**
<summary>
This function configures an interrupt to occur when a message is received on any of the can channels.
</summary>
*/
/**************************************************************************************************************/
void configureCANToInterruptOnRx(InterruptConfig inputInterruptConfig, CanConfig inputCANConfig) {

   int32_t cardIndex = inputCANConfig.cardIndex;
   int32_t module = inputCANConfig.module;
   int32_t vector = NAI_CAN_INTERRUPT_VECTOR;
   int32_t interrupt_Edge_Trigger = inputInterruptConfig.interrupt_Edge_Trigger;
   int32_t steering = inputInterruptConfig.steering;

   nai_can_status_type_t type = NAI_CAN_STATUS_RECV_MSG_LATCHED;

   /* clear interruptConfigs of previous channels */
   check_status(naibrd_CAN_SetIntEnableRaw(cardIndex, module, 0));
   check_status(naibrd_CAN_ClearStatusRaw(cardIndex, module, type, 0xFFFF));

   check_status(naibrd_CAN_SetIntVector(cardIndex, module, 1, type, vector));
   check_status(naibrd_CAN_SetInterruptEdgeLevel(cardIndex, module, type, interrupt_Edge_Trigger));  /* Edge triggered */
   check_status(naibrd_CAN_SetInterruptSteering(cardIndex, module, type, steering));
}

/**************************************************************************************************************/
/**
<summary>
DisplayMessage_CANInterrupt handles displaying the messages associated with the msgId passed in.
</summary>
*/
/**************************************************************************************************************/

void DisplayMessage_CANInterrupt(int32_t msgId)
{
   switch (msgId)
   {
      case (int32_t)MSG_BANNER_CAN_INT:
      {
         printf("\n********************************************************************************");
         printf("\n******                        CAN INTERRUPT                               ******");
         printf("\nAn interrupt will occur when the CAN module receives a message				   ");
         printf("\n********************************************************************************");
      }
      break;

      case (int32_t)MSG_USER_TRIGGER_CAN_INT:
      {
         printf("\nPress \"Q\" to quit the application.\nPlease trigger CAN interrupt (Recv Msg):");
      }
      break;

      case (int32_t)MSG_USER_CLEAR_CAN_INT:
      {
         printf("Press \"C\" to clear interrupts... ");
      }
      break;
   }
}
/**************************************************************************************************************/
/**
<summary>
Enables the channels within the (minChannel,maxChannel) range to interrupt
if enable is true and disables them otherwise.
</summary>
*/
/**************************************************************************************************************/
void enableCANInterrupts(CanConfig inputCANConfig, bool_t enable) {
   int32_t channel;

   int32_t enableRegVal = 0;
   int32_t isEnabled = 0;
   for (channel = inputCANConfig.minChannel; channel <= inputCANConfig.maxChannel; channel++)
   {
      isEnabled = 1 << (channel - 1);

      if (enable)
         enableRegVal = (1 << (channel - 1)) | enableRegVal;
      else if (isEnabled  & enableRegVal)
         enableRegVal = (1 << (channel - 1)) ^ enableRegVal;
   }
   naibrd_CAN_SetIntEnableRaw(inputCANConfig.cardIndex, inputCANConfig.module, enableRegVal);
}

/**************************************************************************************************************/
/**
<summary>
GetCANLatchStatusTriggerMode handles prompting the user for the trigger mode for the latched status register
(Edge Triggered or Level Triggered).
</summary>
*/
/**************************************************************************************************************/
bool_t GetCANLatchStatusTriggerMode(int32_t* interrupt_Edge_Trigger)
{
   bool_t bQuit;
   uint32_t temp;
   int8_t inputBuffer[80];
   int32_t inputResponseCnt;

   printf("\nEnter Latched Status Trigger Mode (Edge=0, Level=1) (Default=0): ");
   bQuit = naiapp_query_ForQuitResponse(sizeof(inputBuffer), NAI_QUIT_CHAR, inputBuffer, &inputResponseCnt);
   if (!bQuit)
   {
      if (inputResponseCnt > 0)
      {
         temp = (int32_t)atol((const char*)inputBuffer);

         if (temp == 0 || temp == 1)
         {
            *interrupt_Edge_Trigger = temp;
         }
         else
         {
            printf("ERROR: Invalid Interrupt Trigger Mode.\n");
         }
      }
      else
         *interrupt_Edge_Trigger = 0;
   }
   return(bQuit);
}

/**************************************************************************************************************/
/**
<summary>
The routine is called to handle a interrupt. This function alerts checkForInterrupt that an interrupt has occurred.
In vxWorks you must clear the Interrupt on the board within the ISR.
</summary>
*/
/**************************************************************************************************************/

#if defined (__VXWORKS__)
void basic_ISR_CAN(uint32_t param)
#else
void basic_ISR_CAN(void *param, uint32_t vector)
#endif
{
   interruptOccured = TRUE;

#if defined (__VXWORKS__)
   interruptVector = nai_Onboard_GetInterruptVector();
   nai_Onboard_ClearInterrupt();
#elif defined (WIN32)
   UNREFERENCED_PARAMETER(param);
   interruptVector = vector;
#else
   interruptVector = vector;
#endif
}

/**************************************************************************************************************/
/**
<summary>
This routine takes in the vector of the CAN RX interrupt that has just occurred. And gets the status, fifo status,
and fifo data of all channels that interrupted, and prints it.
</summary>
*/
/**************************************************************************************************************/
void handleCANInterrupt(uint32_t nVector) {

   FIFO* fifo[NAI_GEN5_CAN_MAX_CHANNEL_COUNT];

   uint8_t status;
   int32_t maxChannel;
   int32_t channel;
   maxChannel = inputCANConfig.maxChannel;

   printf("\n\nInterrupt Occurred \n\n");
   if (inputInterruptConfig.bPromptForInterruptClear)
   {
      promptUserToClearInterrupt_CAN();
   }
   naibrd_CAN_GetStatusRaw(inputCANConfig.cardIndex, inputCANConfig.module, NAI_CAN_STATUS_RECV_MSG_LATCHED, &status);
   naibrd_CAN_ClearStatusRaw(inputCANConfig.cardIndex, inputCANConfig.module, NAI_CAN_STATUS_RECV_MSG_LATCHED, status);

   for (channel = inputCANConfig.minChannel; channel <= inputCANConfig.maxChannel; channel++)
   {
      fifo[channel - 1] = allocateSpaceForFIFO(MAX_FIFO_COUNT, inputCANConfig.maxPayload);
   }

   getFIFODataFromInterruptChannels(status, fifo, inputCANConfig.minChannel, maxChannel);

   printInterruptInformation_CAN(inputCANConfig, nVector, status, fifo, FALSE, fifoDataFile);

   for (channel = inputCANConfig.minChannel; channel < inputCANConfig.maxChannel; channel++)
   {
      deallocSpaceForFIFO(fifo[channel - 1]);
      fifo[channel - 1] = NULL;
   }

}
/**************************************************************************************************************/
/**
<summary>
Gets the fifo data of all channels whose status bit are set in status.
</summary>
*/
/**************************************************************************************************************/

void getFIFODataFromInterruptChannels(uint8_t status, FIFO* fifoData[NAI_GEN5_CAN_MAX_CHANNEL_COUNT], int32_t minChannel, int32_t maxChannel)
{
   int32_t channel = 1;
   uint8_t statusBitSet;
   int32_t cardIndex = inputCANConfig.cardIndex;
   int32_t module = inputCANConfig.module;
   int32_t bufferLength;
   CanDataFrame* buffer;
   uint32_t* fifoStatus;
   for (channel = minChannel; channel <= maxChannel; ++channel)
   {
      /* Check all status elements */
      statusBitSet = status & (1 << (channel - 1));
      if (statusBitSet && fifoData[channel - 1] != NULL)
      {
         buffer = fifoData[channel - 1]->buffer;
         bufferLength = fifoData[channel - 1]->maxFramesOnFifo;
         fifoStatus = &fifoData[channel - 1]->fifoStatus;

         fifoData[channel - 1]->numOfFramesOnFifo = getFIFOChannelData(cardIndex, module, channel, buffer, bufferLength);
         check_status(naibrd_CAN_GetFifoStatusRaw(cardIndex, module, channel, fifoStatus));
         frameCount = frameCount + (fifoData[channel - 1]->numOfFramesOnFifo);
      }
   }

}

/**************************************************************************************************************/
/**
<summary>
Function will print the fifoData provided for each channel that has been set in the status register.
It will also print the status and idr id or vector.
</summary>
*/
/**************************************************************************************************************/

void printInterruptInformation_CAN(CanConfig inputCANConfig, int32_t interruptID, uint8_t status, FIFO* fifoData[NAI_GEN5_CAN_MAX_CHANNEL_COUNT], bool_t isEther, FILE* stream)
{
   uint8_t statusBitSet;
   int32_t channel;
   uint32_t fifoStatus;
   CanDataFrame* buffer;
   int32_t bufferLength;

   printf("\n");
   fprintf(stream, "Interrupt Information\n");
   fprintf(stream, "-----------------------------------\n");
   if (isEther)
      fprintf(stream, "\nIDR ID = %#x \n", interruptID);
   else
      fprintf(stream, "\nVector = %#x \n", interruptID);
   fprintf(stream, "Status = %#x \n", status);

   for (channel = inputCANConfig.minChannel; channel <= inputCANConfig.maxChannel; ++channel)
   {

      statusBitSet = status & (1 << (channel - 1));
      if (statusBitSet && fifoData[channel - 1] != NULL)
      {
         fifoStatus = fifoData[channel - 1]->fifoStatus;
         buffer = fifoData[channel - 1]->buffer;
         bufferLength = fifoData[channel - 1]->numOfFramesOnFifo;
         fprintf(stream, "\n");
         printFIFOStatusRx(fifoStatus, stream);
         fprintf(stream, "\n");
         printFIFOChannelData(channel, buffer, bufferLength, stream);
         fprintf(stream, "\n\n");
      }
   }
   fprintf(stream, "\nFrames Rx = %d", frameCount);
   fprintf(stream, "\n-----------------------------------\n");
}

void promptUserToClearInterrupt_CAN()
{
   /* Prompt the user to clear the interrupt received */
   SetUserRequestClearInt(FALSE);
   printf("\n");
   DisplayMessage_CANInterrupt(MSG_USER_CLEAR_CAN_INT);

   /* Wait for the user to respond */
   while (!GetUserRequestClearInt())
   {
      nai_msDelay(10);
   }
}

void ClearInterrupt_CAN()
{
   nai_status_bit_t status;
   if (inputInterruptConfig.bPromptForInterruptClear)
   {
      promptUserToClearInterrupt_CAN();
   }
   naibrd_CAN_GetStatusRaw(inputCANConfig.cardIndex, inputCANConfig.module, NAI_CAN_STATUS_RECV_MSG_LATCHED, &status);
   naibrd_CAN_ClearStatusRaw(inputCANConfig.cardIndex, inputCANConfig.module, NAI_CAN_STATUS_RECV_MSG_LATCHED, status);
}

Help Bot

X