IOCT Interrupts
Edit this on GitLab
IOCT Interrupts Sample Application (SSK 1.x)
Overview
The IOCT Interrupts sample application demonstrates how to configure and handle hardware interrupts on I/O Control and Timing (IOCT) modules using the NAI Software Support Kit (SSK 1.x). IOCT modules provide high-speed, synchronous serial communication channels with transmit and receive FIFOs, and interrupts allow your application to respond immediately when protocol-level events occur — such as the end of a block transmission, a control word received on the bus, or a FIFO threshold crossed.
Unlike discrete module interrupts (which fire on voltage transitions at I/O pins), IOCT interrupts are protocol-level events tied to the serial communication state machine. They notify your application when the transmit engine has completed a block, when the receive engine has captured a control word or end-of-block marker, or when the FIFO buffers reach capacity. This distinction is important: a DT interrupt fires because a voltage crossed a threshold. An IOCT interrupt fires because the serial protocol engine completed a specific operation.
This sample supports the following IOCT module types:
-
PE — Multi-channel IOCT module. PE interrupt support is noted as under development in the current SSK 1.x release; the sample prints a message and returns if a PE module is selected.
-
SC4 — Single-channel Maximillion IOCT module (
NAI_MODULE_ID_SC4). This is the fully supported interrupt target in the current sample.
The sample installs an onboard ISR (Interrupt Service Routine) that runs on the board’s ARM processor. For Ethernet-connected hosts, this is the standard interrupt delivery mechanism — the ISR executes on the board, sets flags, and the host application polls those flags. This sample does not use the Ethernet IDR (Interrupt Driven Response) variant. For background on interrupt concepts — including edge vs. level triggering, interrupt vector numbering, steering architecture, and the Ethernet IDR mechanism — see the Interrupts API Guide.
For basic IOCT channel configuration (command register, data rate, FIFO operations, loopback), see the IOCT BasicOps sample application guide. This guide focuses specifically on the interrupt configuration and handling path.
Prerequisites
Before running this sample, make sure you have:
-
An NAI board with an IOCT module installed (SC4 for full interrupt support, or PE for basic detection).
-
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.
-
An IOCT communication partner — either another IOCT channel on the same module (via loopback), or an external device transmitting IOCT data on the bus — to generate the events that trigger interrupts.
How to Run
Launch the IOCT_Interrupts executable from your build output directory. On startup the application looks for a configuration file (default_IOCT_Interrupt.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 configures interrupts on channel 1 and waits for interrupt events.
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 IOCT. For details on board connection configuration, see the First Time Setup Guide. |
The main() function (or IOCT_Interrupts() on VxWorks) follows a standard SSK 1.x startup flow:
-
Call
naiapp_RunBoardMenu()to load a saved configuration file or present the interactive board menu. The configuration file (default_IOCT_Interrupt.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 will always appear. -
Query the user for card index with
naiapp_query_CardIndex(). -
Query the user for module number with
naiapp_query_ModuleNumber(). -
Retrieve the module ID with
naibrd_GetModuleID()to verify an IOCT module is installed at the selected slot and to determine which variant (PE or SC4) is present.
#if defined (__VXWORKS__)
int32_t IOCT_Interrupts(void)
#else
int32_t main(void)
#endif
{
bool_t stop = FALSE;
int32_t cardIndex;
int32_t moduleCnt;
int32_t module;
uint32_t moduleID = 0;
int8_t inputBuffer[80];
int32_t inputResponseCnt;
if (naiapp_RunBoardMenu(CONFIG_FILE) == TRUE)
{
while (stop != TRUE)
{
stop = naiapp_query_CardIndex(naiapp_GetBoardCnt(), 0, &cardIndex);
if (stop != TRUE)
{
check_status(naibrd_GetModuleCount(cardIndex, &moduleCnt));
stop = naiapp_query_ModuleNumber(moduleCnt, 1, &module);
if (stop != TRUE)
{
moduleID = naibrd_GetModuleID(cardIndex, module);
if (moduleID != 0)
{
Run_IOCT_Interrupt(cardIndex, module, moduleID);
}
}
}
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:
|
Program Structure
The IOCT Interrupts sample consists of a single source file:
-
IOCT_Interrupts.c— entry point, interrupt configuration, ISR, and interrupt handling loop. Also references shared utility functions fromnai_ioct_utils.c.
Entry Point
After board connection succeeds, main() calls Run_IOCT_Interrupt() with the card index, module number, and module ID. The module ID determines which interrupt configuration path to follow — currently, only SC4 has full interrupt support.
Build-Time Configuration
The sample uses a compile-time #define to select between two interrupt delivery modes:
-
RUN_IOCT_ISRdefined (default) — the sample installs a hardware ISR vianaibrd_InstallISR(). When an interrupt fires, the ISR sets a flag indicating which channel received the interrupt. -
RUN_IOCT_ISRnot defined — the sample polls the interrupt status registers directly in the main loop viacheckForIOCTStatusChange(). This is useful when a hardware ISR is not available or practical for your deployment.
Both modes configure the same underlying interrupt registers. The difference is only in how your application detects that an interrupt has occurred. The menu system is a sample convenience — in your own code, call these API functions directly.
Global State
The sample tracks interrupt state in global variables that are shared between the ISR and the main loop:
uint32_t irqIOCTCount = 0;
static uint32_t receivedVector = 0;
static bool_t bReceivedIOCTInt_Ch1 = FALSE, bReceivedIOCTInt_Ch2 = FALSE;
#define IOCT_INTERRUPT_VECTOR_CH1 0x40
#define IOCT_INTERRUPT_VECTOR_CH2 0x41
-
irqIOCTCount— running count of all IOCT interrupts received. Useful for debugging to confirm interrupts are firing. -
receivedVector— the vector value from the most recent interrupt. -
bReceivedIOCTInt_Ch1/bReceivedIOCTInt_Ch2— per-channel flags set by the ISR and consumed by the main loop. -
IOCT_INTERRUPT_VECTOR_CH1/IOCT_INTERRUPT_VECTOR_CH2— vector constants assigned to each channel. These allow the ISR to identify which channel generated the interrupt.
IOCT Interrupt Groups
IOCT interrupts are organized into two groups, each covering a distinct set of protocol events:
Group A — Transmit Events
Group A interrupts monitor the transmit side of the IOCT channel. The sample enables two Group A sources on channel 1:
-
IOCT_GROUP_A_EOB_XMITTED— End of Block transmitted. The transmit engine has finished sending a complete data block. This is your notification that the channel is ready for the next block. -
IOCT_GROUP_A_TX_FIFO_FULL— Transmit FIFO full. The transmit buffer has reached capacity. If you continue to write data without waiting for this condition to clear, data will be lost.
Additional Group A sources (not enabled in this sample) include Tx FIFO empty, Tx FIFO overflow, word transmitted, and segment active status. Consult your SC4 module manual for the complete Group A interrupt bitmask definitions.
Group B — Receive Events
Group B interrupts monitor the receive side of the IOCT channel. The sample enables three Group B sources on channel 1:
-
IOCT_GROUP_B_CTRL_WORD_RECVD— Control word received. The receive engine has captured a control word from the bus. Control words carry protocol-level commands separate from data payload. -
IOCT_GROUP_B_EOB_RECVD— End of Block received. A complete data block has been received and is available in the receive FIFO. -
IOCT_GROUP_B_RX_FIFO_FULL— Receive FIFO full. The receive buffer has reached capacity. If new data arrives before the application reads the FIFO, data will be lost.
Additional Group B sources include parity error, no clock, no SEP, Rx FIFO empty, Rx FIFO overflow, and SEP high/low status. Consult your SC4 module manual for the complete Group B interrupt bitmask definitions.
How IOCT Interrupts Differ from Discrete Interrupts
| Characteristic | DT (Discrete) Interrupts | IOCT Interrupts |
|---|---|---|
What is monitored |
Voltage levels on I/O pins |
Serial protocol events (block completion, FIFO status, control words) |
Event granularity |
Per-pin voltage transitions |
Per-channel protocol state changes |
Interrupt groups |
Single group per channel (BIT, Lo-Hi, Hi-Lo) |
Two groups per channel: Group A (Tx events) and Group B (Rx events) |
Handler action |
Read which channels triggered, check signal state |
Read latched status to identify which protocol event fired, then read FIFO if data available |
Prerequisite |
Physical signal present at input pin |
Active IOCT communication on the bus (transmitter and receiver configured) |
Interrupt Configuration
The Run_IOCT_Interrupt() function configures the module’s interrupt registers for channel 1 on an SC4 module. The configuration follows a specific sequence: install ISR, clear stale status, set trigger mode, assign vectors, configure steering, and enable interrupt sources.
Step 1: Install the ISR
Before configuring any interrupt registers, install the ISR that will handle interrupt events. The sample installs MyIOCTIsr on the NAIBRD_IRQ_ID_ON_BOARD_0 IRQ line:
check_status(naibrd_InstallISR(cardIndex, NAIBRD_IRQ_ID_ON_BOARD_0, (nai_isr_t)MyIOCTIsr, NULL));
This installs the ISR on the board’s onboard processor (ARM). For Ethernet-connected hosts, this is the standard interrupt delivery path — the ISR runs on the board, and the SSK framework relays the notification to the host application over the Ethernet connection. The NAIBRD_IRQ_ID_ON_BOARD_0 identifier must match the steering mode configured in Step 5.
Step 2: Clear Stale Interrupt Status
Always clear stale interrupt status before enabling interrupts. Stale latched status from a previous run or power-on state will trigger an immediate interrupt if not cleared first:
ClearInterruptStatus(modid, cardIndex, module, channel, NAI_IOCT_GROUP_ALL, 0xFFFFFFFF, &latched, &realtime);
DisplayIOCTInterruptStatus(cardIndex, module, modid);
ClearInterruptStatus() is a shared utility from nai_ioct_utils.c. For the SC4 module, it calls naibrd_IOCT_GetInterruptStatus() to read the current latched status, then naibrd_IOCT_ClearInterruptStatus() to write-clear all latched bits. The NAI_IOCT_GROUP_ALL parameter clears both Group A (Tx) and Group B (Rx) status registers in a single call. The 0xFFFFFFFF mask clears all bits.
DisplayIOCTInterruptStatus() prints a formatted table of all channels' realtime and latched interrupt status. This is useful for verifying that the clear operation succeeded.
|
Note
|
The PE module uses a different clear mechanism — reading the latched status register automatically clears it (read-to-clear). The SC4 module uses write-to-clear, where you explicitly write a 1 to each bit you want to clear. The utility function handles this difference internally based on the module ID.
|
Step 3: Set Trigger Mode
Configure the interrupt trigger mode for the channel. The sample uses edge triggering:
check_status(naibrd_IOCT_SetEdgeLevelInterrupt(cardIndex, module, channel, NAI_IOCT_EDGE_INTERRUPT));
-
Edge-triggered (
NAI_IOCT_EDGE_INTERRUPT) — the interrupt fires once when the status transitions from clear to set. Use this when you want a single notification per event. -
Level-triggered — the interrupt fires continuously as long as the status remains set. Use this when events may arrive faster than your handler processes them and you need continuous notification.
For IOCT interrupts, edge triggering is appropriate when you expect discrete events (end-of-block, control word received) and your handler clears the status after each event. Level triggering is more appropriate for FIFO-full conditions where the interrupt should persist until the application drains the FIFO.
Step 4: Set Interrupt Vectors
Assign an interrupt vector to the channel. The vector identifies which channel generated the interrupt when the ISR is called:
check_status(naibrd_IOCT_SetInterruptVector(cardIndex, module, channel, NAI_IOCT_GROUP_ALL, IOCT_INTERRUPT_VECTOR_CH1));
The sample assigns vector 0x40 to channel 1 and (if a second channel were configured) vector 0x41 to channel 2. The NAI_IOCT_GROUP_ALL parameter applies the same vector to both Group A and Group B interrupt sources on this channel. If you need to distinguish between Tx and Rx events at the ISR level, you can assign different vectors to NAI_IOCT_GROUP_A and NAI_IOCT_GROUP_B separately.
Step 5: Set Interrupt Steering
Steering determines how the interrupt signal is delivered from the module to your application:
check_status(naibrd_IOCT_SetInterruptSteering(cardIndex, module, channel, NAIBRD_INT_STEERING_ON_BOARD_0));
The sample uses NAIBRD_INT_STEERING_ON_BOARD_0, which delivers the interrupt to the board’s onboard processor. This must match the IRQ ID used in naibrd_InstallISR() (Step 1). For Ethernet-connected hosts, onboard steering is the standard path — the ISR executes on the board’s ARM processor and the SSK framework handles notification delivery to the host.
Other steering options include:
-
NAIBRD_INT_STEERING_CPCI_APP— cPCI offboard delivery. -
NAIBRD_INT_STEERING_PCIE_APP— PCIe offboard delivery. -
NAIBRD_INT_STEERING_ON_BOARD_1— Ethernet IDR delivery (for boards with Gen4+ firmware that support the IDR mechanism).
Step 6: Enable Interrupt Sources
Finally, enable the specific interrupt sources for the channel. The sample enables selected Group A (Tx) and Group B (Rx) sources separately:
/* Enable Group A (Tx) interrupts */
interruptmask = IOCT_GROUP_A_EOB_XMITTED | IOCT_GROUP_A_TX_FIFO_FULL;
check_status(naibrd_IOCT_SetInterruptEnable(cardIndex, module, channel, NAI_IOCT_GROUP_A, interruptmask));
/* Enable Group B (Rx) interrupts */
interruptmask = IOCT_GROUP_B_CTRL_WORD_RECVD | IOCT_GROUP_B_EOB_RECVD | IOCT_GROUP_B_RX_FIFO_FULL;
check_status(naibrd_IOCT_SetInterruptEnable(cardIndex, module, channel, NAI_IOCT_GROUP_B, interruptmask));
The interruptmask parameter is a bitmask that selects which interrupt sources to enable. You can OR together multiple source constants to enable several sources in a single call. Each group has its own enable register, so you make two calls — one for Group A and one for Group B.
Full Configuration Sequence
The complete configuration flow in Run_IOCT_Interrupt() for the SC4 module:
/* Install the ISR */
check_status(naibrd_InstallISR(cardIndex, NAIBRD_IRQ_ID_ON_BOARD_0, (nai_isr_t)MyIOCTIsr, NULL));
channel = 1;
/* Clear Group A and Group B interrupts */
ClearInterruptStatus(modid, cardIndex, module, channel, NAI_IOCT_GROUP_ALL, 0xFFFFFFFF, &latched, &realtime);
DisplayIOCTInterruptStatus(cardIndex, module, modid);
/* Set the Interrupt Type to Edge Trigger for all interrupts */
check_status(naibrd_IOCT_SetEdgeLevelInterrupt(cardIndex, module, channel, NAI_IOCT_EDGE_INTERRUPT));
/* Set the Interrupt Vectors for the IOCT channel */
check_status(naibrd_IOCT_SetInterruptVector(cardIndex, module, channel, NAI_IOCT_GROUP_ALL, IOCT_INTERRUPT_VECTOR_CH1));
/* Set the Interrupt Steering to the application running on the ARM */
check_status(naibrd_IOCT_SetInterruptSteering(cardIndex, module, channel, NAIBRD_INT_STEERING_ON_BOARD_0));
/* Enable Channel 1 Interrupts */
interruptmask = IOCT_GROUP_A_EOB_XMITTED | IOCT_GROUP_A_TX_FIFO_FULL;
check_status(naibrd_IOCT_SetInterruptEnable(cardIndex, module, channel, NAI_IOCT_GROUP_A, interruptmask));
interruptmask = IOCT_GROUP_B_CTRL_WORD_RECVD | IOCT_GROUP_B_EOB_RECVD | IOCT_GROUP_B_RX_FIFO_FULL;
check_status(naibrd_IOCT_SetInterruptEnable(cardIndex, module, channel, NAI_IOCT_GROUP_B, interruptmask));
|
Important
|
Common interrupt configuration errors:
|
Interrupt Handler (ISR)
The ISR (MyIOCTIsr) is intentionally minimal. It identifies which channel generated the interrupt based on the vector value, sets the corresponding flag, and returns. All heavy processing — status display, FIFO reads, status clearing — happens in the main thread.
void MyIOCTIsr(void* param, uint32_t vector)
{
irqIOCTCount++;
/* Determine what interrupt was received */
receivedVector = vector;
switch (vector)
{
case IOCT_INTERRUPT_VECTOR_CH1:
bReceivedIOCTInt_Ch1 = TRUE;
break;
case IOCT_INTERRUPT_VECTOR_CH2:
bReceivedIOCTInt_Ch2 = TRUE;
break;
default:
printf("Unknown Interrupt, Vector:0x%02X, IOCT irqCount:%d\n", vector, irqIOCTCount);
break;
}
}
The ISR receives the interrupt vector as a parameter. The switch statement maps the vector to the corresponding channel flag. If an unexpected vector arrives, the ISR prints a diagnostic message — in your own application, you would log this for debugging rather than printing from an ISR context.
In your own application, keep the ISR as short as possible. Set a flag or post a message, then do the heavy work (status reads, FIFO operations, data processing) outside the ISR context. The sample follows this pattern.
VxWorks Variant
On VxWorks, the ISR signature differs slightly — it receives a single uint32_t parameter and must explicitly read the interrupt vector and clear the interrupt:
#if defined (__VXWORKS__)
void MyIOCTIsr(uint32_t nVector)
{
unsigned int vector = nai_Onboard_GetInterruptVector();
nai_Onboard_ClearInterrupt();
/* ... same switch logic ... */
}
#endif
Interrupt Handling Loop
After configuring interrupts, the main thread enters a polling loop that checks the ISR-set flags and processes interrupt events when they occur.
ISR-Based Handling (Default)
When RUN_IOCT_ISR is defined (the default build configuration), the main loop checks the bReceivedIOCTInt_Ch1 and bReceivedIOCTInt_Ch2 flags that the ISR sets:
while (bContinue)
{
if (bReceivedIOCTInt_Ch1 || bReceivedIOCTInt_Ch2)
{
if (bReceivedIOCTInt_Ch1)
{
channel = 1;
printf("Received IOCT Interrupt on Channel 1\n");
bReceivedIOCTInt_Ch1 = FALSE;
}
if (bReceivedIOCTInt_Ch2)
{
channel = 2;
printf("Received IOCT Interrupt on Channel 2\n");
bReceivedIOCTInt_Ch2 = FALSE;
}
if (modid == NAI_MODULE_ID_SC4)
{
/* Display the latched and realtime interrupt status */
DisplayIOCTInterruptStatus(cardIndex, module, modid);
/* Retrieve the interrupt latched status */
naibrd_IOCT_GetInterruptStatus(cardIndex, module, channel, NAI_IOCT_GROUP_ALL, NAI_IOCT_INTERRUPT_STATUS_LATCHED, &latched);
/* Clear the interrupt status */
naibrd_IOCT_ClearInterruptStatus(cardIndex, module, channel, NAI_IOCT_GROUP_ALL, latched);
}
else
{
/* PE module: reading the latched register clears it */
naibrd_IOCT_GetInterruptStatus(cardIndex, module, channel, NAI_IOCT_GROUP_ALL, NAI_IOCT_INTERRUPT_STATUS_LATCHED, &latched);
}
}
}
When a flag is set, the handler:
-
Identifies the channel — checks which per-channel flag is set and clears it.
-
Displays interrupt status — calls
DisplayIOCTInterruptStatus()to print a formatted table of all channels' realtime and latched status values, showing which specific interrupt sources are active. -
Reads the latched status — calls
naibrd_IOCT_GetInterruptStatus()withNAI_IOCT_INTERRUPT_STATUS_LATCHEDto retrieve the current latched status word. This tells you exactly which Group A and Group B events triggered. -
Clears the latched status — on the SC4, calls
naibrd_IOCT_ClearInterruptStatus()with the latched value to write-clear only the bits that were set. On the PE, reading the latched register is sufficient to clear it (read-to-clear behavior).
Polling-Based Handling (Alternative)
When RUN_IOCT_ISR is not defined, the sample uses a polling function instead of an ISR. The checkForIOCTStatusChange() function iterates over all channels and reads their latched interrupt status directly:
void checkForIOCTStatusChange(int32_t cardIndex, int32_t module, int32_t maxchannel)
{
uint32_t latched;
int32_t channel;
for (channel = 1; channel < maxchannel; channel++)
{
naibrd_IOCT_GetInterruptStatus(cardIndex, module, channel, NAI_IOCT_GROUP_ALL, NAI_IOCT_INTERRUPT_STATUS_LATCHED, &latched);
if (latched != 0)
{
irqIOCTCount++;
if (channel == 1)
{
receivedVector = IOCT_INTERRUPT_VECTOR_CH1;
bReceivedIOCTInt_Ch1 = TRUE;
}
else if (channel == 2)
{
receivedVector = IOCT_INTERRUPT_VECTOR_CH2;
bReceivedIOCTInt_Ch2 = TRUE;
}
}
}
}
This approach does not require naibrd_InstallISR() and works in environments where hardware ISR installation is not available. It achieves the same result — detecting interrupt status changes and setting the per-channel flags — but with higher latency since detection depends on your polling interval.
In your own application, choose the approach that matches your deployment:
-
ISR-based — lowest latency, suitable for real-time systems where immediate response to protocol events is required.
-
Polling-based — simpler to integrate, no ISR installation required, but detection latency depends on your polling rate.
|
Important
|
Common interrupt handling errors:
|
Ethernet Interrupt Delivery
For Ethernet-connected hosts, understanding the interrupt delivery path is important for troubleshooting. When you call naibrd_InstallISR() with NAIBRD_IRQ_ID_ON_BOARD_0, the ISR is installed on the board’s onboard ARM processor. The interrupt flow is:
-
The IOCT module detects an enabled interrupt condition (Group A or Group B event).
-
The module asserts the interrupt on the internal bus with the configured vector.
-
The onboard ARM processor receives the interrupt and calls your ISR (
MyIOCTIsr). -
Your ISR sets a flag (e.g.,
bReceivedIOCTInt_Ch1 = TRUE). -
The host application, connected via Ethernet, reads the flag state through the SSK’s Ethernet transport layer.
This means the ISR executes on the board, not on your host machine. The latency between the interrupt firing and your host application detecting the flag depends on the Ethernet polling interval. For applications that require the board to proactively push interrupt data to the host over the network, consider the Ethernet IDR (Interrupt Driven Response) mechanism described in the Interrupts API Guide. The IDR approach uses naibrd_Ether_SetIDRConfig() and naibrd_Ether_StartIDR() to configure the board to automatically read registers and send the results as TCP/UDP messages when an interrupt fires — but this sample does not demonstrate that variant.
Module-Specific Behavior
SC4 Module
The SC4 is the fully supported interrupt target in this sample. Key SC4-specific behaviors:
-
Write-to-clear status — latched status bits are cleared by writing a
1to the bits you want to clear vianaibrd_IOCT_ClearInterruptStatus(). Simply reading the status does not clear it. -
Single channel — the SC4 has one IOCT channel. The sample configures channel 1. Vector
0x40is assigned to this channel. -
Both interrupt groups — the SC4 supports both Group A (Tx) and Group B (Rx) interrupt sources. The sample enables a subset of each.
PE Module
The PE module has multi-channel IOCT capability, but the current SSK 1.x sample does not implement full interrupt configuration for it:
if (modid == NAI_MODULE_ID_SC4)
{
/* ... full SC4 interrupt configuration ... */
}
else
{
printf("\nPE interrupt example currently under development\n");
return TRUE;
}
If you need to configure PE module interrupts in your own application, the API calls are the same (naibrd_IOCT_SetEdgeLevelInterrupt(), naibrd_IOCT_SetInterruptVector(), etc.), but be aware of the PE-specific differences:
-
Read-to-clear status — reading the PE’s latched status register automatically clears the bits. You do not need to call
naibrd_IOCT_ClearInterruptStatus(). -
Multiple channels — the PE supports multiple IOCT channels. You would configure interrupts for each channel individually, assigning unique vectors to distinguish them in the ISR.
Consult your PE module manual for the complete list of supported interrupt sources and any PE-specific register differences.
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 IOCT Interrupts That Are Not Firing
When interrupts are not being delivered, use this two-step approach to isolate the problem:
-
Check whether the status registers are changing. Call
naibrd_IOCT_GetInterruptStatus()withNAI_IOCT_INTERRUPT_STATUS_LATCHEDfor bothNAI_IOCT_GROUP_AandNAI_IOCT_GROUP_Bto read the latched status for your channel. If the status bits are setting when you know the bus is active, the module is detecting events correctly — the issue is in your interrupt delivery path (steering, ISR installation, or enable mask). -
If the status registers are NOT changing, verify that the IOCT channel is configured and active (Tx enabled on the sending side, Rx enabled on the receiving side), that an IOCT communication partner is present on the bus, and that the data rate and protocol settings match between the two endpoints. You can use the
DisplayIOCTInterruptStatus()utility to check realtime status, which reflects the current hardware state without latching. Also verify that the channel is properly configured using the IOCT BasicOps sample as a reference.
| 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 as IOCT |
No IOCT module installed at the selected slot, incorrect module number, or a non-IOCT module is present |
Verify hardware configuration. Check |
"PE interrupt example currently under development" |
A PE module was selected. The current SSK 1.x sample only supports SC4 interrupts. |
Use an SC4 module for this sample. For PE interrupt configuration, use the same API calls with PE-specific status clearing (read-to-clear instead of write-to-clear). |
Interrupts never fire despite active bus traffic |
Interrupts not enabled, wrong group specified, steering mismatch, or ISR not installed |
Verify |
Spurious interrupt on startup |
Stale latched status from previous run or power-on state |
Always clear all status registers before enabling interrupts. Use |
Interrupts stop after first event |
Status register not cleared after handling (edge-triggered mode) |
Call |
SC4 status persists after clearing |
Writing zero to the clear register, or writing the wrong bits |
The SC4 uses write-to-clear: write a |
Unknown vector in ISR |
Vector assignment does not match the channel’s configured vector, or an unrelated interrupt was delivered |
Verify |
Steering mismatch — ISR never called |
Steering mode does not match the IRQ ID used in |
Match steering to where your application executes. Onboard: |
High interrupt rate causes missed events |
Edge-triggered mode and events arriving faster than the handler clears status |
Switch to level-triggered mode for FIFO-full conditions. For event-driven interrupts (EOB, control word), ensure your handler clears status promptly after each event. |
Full Source
The complete source for this sample is provided below for reference. The sections above explain each part in detail.
Full Source — IOCT_Interrupts.c (SSK 1.x)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.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"
#include "functions/naibrd_ioct.h"
#include "advanced/nai_ether_adv.h"
#include "nai_ioct_utils.h"
static const int8_t *CONFIG_FILE = (const int8_t *)"default_IOCT_Interrupt.txt";
#define RUN_IOCT_ISR
/* Function prototypes */
static bool_t Run_IOCT_Interrupt(int32_t cardIndex, int32_t module, uint32_t modid);
#if defined (__VXWORKS__)
void MyIOCTIsr(uint32_t nVector);
#else
void MyIOCTIsr(void* param,uint32_t vector);
#endif
void checkForIOCTStatusChange(int32_t cardIndex, int32_t module, int32_t maxchan);
uint32_t irqIOCTCount = 0;
static uint32_t receivedVector = 0;
static bool_t bReceivedIOCTInt_Ch1 = FALSE, bReceivedIOCTInt_Ch2 = FALSE;
#define IOCT_INTERRUPT_VECTOR_CH1 0x40
#define IOCT_INTERRUPT_VECTOR_CH2 0x41
/*****************************************************************************/
/**
<summary>
The purpose of the IOCT_Interrupts is to illustrate the methods to call in the
naibrd library to perform interrupt operations with the IOCT modules.
The following system configuration routines from the nai_sys_cfg.c file are
called to assist with the configuration setup for this program prior to
calling the naibrd IOCT routines.
- ConfigDevice
- DisplayDeviceCfg
- GetBoardSNModCfg
- CheckModule
</summary>
*/
/*****************************************************************************/
#if defined (__VXWORKS__)
int32_t IOCT_Interrupts(void)
#else
int32_t main(void)
#endif
{
bool_t stop = FALSE;
int32_t cardIndex;
int32_t moduleCnt;
int32_t module;
uint32_t moduleID = 0;
int8_t inputBuffer[80];
int32_t inputResponseCnt;
if (naiapp_RunBoardMenu(CONFIG_FILE) == TRUE)
{
while (stop != TRUE)
{
/* Query the user for the card index */
stop = naiapp_query_CardIndex(naiapp_GetBoardCnt(), 0, &cardIndex);
if (stop != TRUE)
{
check_status(naibrd_GetModuleCount(cardIndex, &moduleCnt));
/* Query the user for the module number */
stop = naiapp_query_ModuleNumber(moduleCnt, 1, &module);
if (stop != TRUE)
{
moduleID = naibrd_GetModuleID(cardIndex, module);
if (moduleID != 0)
{
Run_IOCT_Interrupt(cardIndex, module, moduleID);
}
}
}
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>
Run_IOCT_Interrupt illustrates the methods to call in the
naibrd library to configure IOCT interrupts.
</summary>
*/
/*****************************************************************************/
static bool_t Run_IOCT_Interrupt(int32_t cardIndex, int32_t module, uint32_t modid)
{
bool_t bContinue = TRUE;
int32_t channel;
uint32_t latched, realtime;
nai_ioct_group_interrupt_t interruptmask;
#if !defined (RUN_IOCT_ISR)
int32_t maxchannel = naibrd_IOCT_GetChannelCount(modid);
#endif
#if defined (RUN_IOCT_ISR)
/* Install the ISR */
check_status(naibrd_InstallISR(cardIndex, NAIBRD_IRQ_ID_ON_BOARD_0, (nai_isr_t)MyIOCTIsr, NULL));
#endif
if (modid == NAI_MODULE_ID_SC4)
{
channel = 1;
/* Clear Group A and Group B interrupts */
ClearInterruptStatus(modid, cardIndex, module, channel, NAI_IOCT_GROUP_ALL, 0xFFFFFFFF, &latched, &realtime);
DisplayIOCTInterruptStatus(cardIndex, module, modid);
/* Set the Interrupt Type to Edge Trigger for all interrupts */
check_status(naibrd_IOCT_SetEdgeLevelInterrupt(cardIndex, module, channel, NAI_IOCT_EDGE_INTERRUPT));
/* Set the Interrupt Vectors for the IOCT channel */
check_status(naibrd_IOCT_SetInterruptVector(cardIndex, module, channel, NAI_IOCT_GROUP_ALL, IOCT_INTERRUPT_VECTOR_CH1));
/* Set the Interrupt Steering to the application running on the ARM */
check_status(naibrd_IOCT_SetInterruptSteering(cardIndex, module, channel, NAIBRD_INT_STEERING_ON_BOARD_0));
/* Enable Channel 1 Interrupts */
interruptmask = IOCT_GROUP_A_EOB_XMITTED | IOCT_GROUP_A_TX_FIFO_FULL;
check_status(naibrd_IOCT_SetInterruptEnable(cardIndex, module, channel, NAI_IOCT_GROUP_A, interruptmask));
interruptmask = IOCT_GROUP_B_CTRL_WORD_RECVD | IOCT_GROUP_B_EOB_RECVD | IOCT_GROUP_B_RX_FIFO_FULL;
check_status(naibrd_IOCT_SetInterruptEnable(cardIndex, module, channel, NAI_IOCT_GROUP_B, interruptmask));
}
else
{
printf("\nPE interrupt example currently under development\n");
return TRUE;
}
printf("\nWaiting for IOCT Interrupt...\n");
while (bContinue)
{
#if !defined (RUN_IOCT_ISR)
checkForIOCTStatusChange(cardIndex, module, maxchannel);
#endif
if (bReceivedIOCTInt_Ch1 || bReceivedIOCTInt_Ch2)
{
if (bReceivedIOCTInt_Ch1)
{
channel = 1;
printf("Received IOCT Interrupt on Channel 1\n");
bReceivedIOCTInt_Ch1 = FALSE;
}
if (bReceivedIOCTInt_Ch2)
{
channel = 2;
printf("Received IOCT Interrupt on Channel 2\n");
bReceivedIOCTInt_Ch2 = FALSE;
}
if (modid == NAI_MODULE_ID_SC4)
{
/* Display the latched and realtime interrupt status */
DisplayIOCTInterruptStatus(cardIndex, module, modid);
/* Retrieve the interrupt latched status */
naibrd_IOCT_GetInterruptStatus(cardIndex, module, channel, NAI_IOCT_GROUP_ALL, NAI_IOCT_INTERRUPT_STATUS_LATCHED, &latched);
/* Clear the interrupt status */
naibrd_IOCT_ClearInterruptStatus(cardIndex, module, channel, NAI_IOCT_GROUP_ALL, latched);
}
else
{
/* Retrieve the interrupt latched status (note, with PE module, reading the register will unlatch/clear the interrupt status */
naibrd_IOCT_GetInterruptStatus(cardIndex, module, channel, NAI_IOCT_GROUP_ALL, NAI_IOCT_INTERRUPT_STATUS_LATCHED, &latched);
}
}
#if defined (__VXWORKS__)
taskDelay(1);
#endif
}
return bContinue;
}
#if defined (RUN_IOCT_ISR)
/*****************************/
/* Interrupt Service Routine */
/*****************************/
#if defined (__VXWORKS__)
void MyIOCTIsr(uint32_t nVector)
#else
void MyIOCTIsr(void* param, uint32_t vector)
#endif
{
#if defined (WIN32)
UNREFERENCED_PARAMETER(param);
#endif
#if defined (__VXWORKS__)
/* Get the vector that caused the interrupt */
unsigned int vector = nai_Onboard_GetInterruptVector();
/* Clear Interrupt */
nai_Onboard_ClearInterrupt();
#endif
irqIOCTCount++;
/* Determine what interrupt was received */
receivedVector = vector;
switch (vector)
{
case IOCT_INTERRUPT_VECTOR_CH1:
bReceivedIOCTInt_Ch1 = TRUE;
break;
case IOCT_INTERRUPT_VECTOR_CH2:
bReceivedIOCTInt_Ch2 = TRUE;
break;
default:
#if defined (__VXWORKS__)
logMsg("Unknown Interrupt, Vector:0x%02X, IOCT irqCount:%d\n", vector, irqIOCTCount,0,0,0,0);
#else
printf("Unknown Interrupt, Vector:0x%02X, IOCT irqCount:%d\n", vector, irqIOCTCount);
#endif
break;
}
}
#else
void checkForIOCTStatusChange(int32_t cardIndex, int32_t module, int32_t maxchannel)
{
uint32_t latched;
int32_t channel;
for (channel = 1; channel < maxchannel; channel++)
{
/* Retrieve the interrupt latched status */
naibrd_IOCT_GetInterruptStatus(cardIndex, module, channel, NAI_IOCT_GROUP_ALL, NAI_IOCT_INTERRUPT_STATUS_LATCHED, &latched);
if (latched != 0)
{
irqIOCTCount++;
if (channel == 1)
{
receivedVector = IOCT_INTERRUPT_VECTOR_CH1;
bReceivedIOCTInt_Ch1 = TRUE;
}
else if (channel == 2)
{
receivedVector = IOCT_INTERRUPT_VECTOR_CH2;
bReceivedIOCTInt_Ch2 = TRUE;
}
}
}
}
#endif