DT Ethernet IDR
Edit this on GitLab
DT Ethernet IDR Sample Application (SSK 1.x)
Overview
The DT Ethernet IDR sample application demonstrates how to handle discrete (DT) module interrupts over Ethernet using Interrupt Driven Response (IDR) commands with the NAI Software Support Kit (SSK 1.x). When a DT interrupt fires, the board’s onboard processor automatically reads the latched status registers and sends the results to your host over Ethernet as an unprompted response — without requiring the host to poll. This eliminates polling overhead and provides low-latency notification of discrete I/O events.
This sample configures and enables four interrupt types: BIT (fault), low-to-high transition, high-to-low transition, and overcurrent. It supports onboard and offboard interrupt steering, and handles the complete Ethernet IDR lifecycle: clearing previous IDR configurations, building the IDR read command, configuring the response destination, starting the IDR, processing incoming interrupt messages, and cleaning up on exit.
This sample supports the following DT module types: DT1, DT4, DTB, CF1, IF3, D1I, D1P, D4I, and D4P. It also works with combination modules that include DT functionality: CM1, CM2, and CM8. For background on interrupt concepts — including edge vs. level triggering, interrupt vector numbering, steering architecture, and latency measurement — see the Interrupts API Guide. For the standard (non-Ethernet) DT interrupt implementation, see the DT Interrupts guide. This guide focuses specifically on the Ethernet IDR delivery mechanism.
Prerequisites
Before running this sample, make sure you have:
-
An NAI Gen4 or later board with a DT module installed (DT1, DT4, DTB, CF1, IF3, D1I, D1P, D4I, D4P, or a combination module such as CM1, CM2, CM8).
-
An Ethernet connection between the board and your development host.
-
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.
-
Network configuration that allows UDP traffic from the board to the host on the configured response port (default: 52802).
How to Run
Launch the DT_Ethernet_IDR executable from your build output directory. On startup the application looks for a configuration file (default_DT_Ethernet_Int.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. After connecting, the application prompts for:
-
Card index, module slot, channel, and trigger mode (edge or level).
-
Ethernet IDR response IP address and port (defaults: 192.168.1.200 port 52802).
-
Whether to display unprompted Ethernet response messages.
-
Whether to prompt before clearing each interrupt.
-
Onboard or offboard interrupt processing.
The application then configures the module, starts the IDR, enables interrupts, and begins waiting 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 DT. For details on board connection configuration, see the First Time Setup Guide. |
The main() function follows the standard SSK 1.x startup flow with additional Ethernet IDR configuration steps:
-
Call
naiapp_RunBoardMenu()to load a saved configuration file (if one exists) or present the interactive board menu. The configuration file (default_DT_Ethernet_Int.txt) is not included with the SSK — it is created when the user saves their connection settings from the board menu. On the first run, the menu will always appear. -
Call
naiapp_access_SystemSNModCfg()to acquire the system serial number and module configuration. -
Call
PromptUserInput_DTInterrupt()to query for card index, module slot, channel, and trigger mode. -
Call
QueryUnpromptedEtherResponseIP4Addr()to configure the IDR response IP address, port, and protocol. -
Call
CheckNaiEtherProtocolVersion()to verify the board supports Gen4 Ethernet commands.
#if defined (__VXWORKS__)
void DT_Ethernet_IDR(void)
#else
int32_t main(void)
#endif
{
bool_t bQuit = FALSE;
bool_t bSuccess = FALSE;
bool_t bProcessOnboardInterrupts = FALSE;
DisplayMessage_DTInterrupt(MSG_BANNER_DT_INT);
if (naiapp_RunBoardMenu(DEF_CONFIG_FILE) == TRUE)
{
while (!bQuit)
{
naiapp_access_SystemSNModCfg();
bQuit = PromptUserInput_DTInterrupt(&bSuccess);
if ((!bQuit) & bSuccess)
{
bQuit = QueryUnpromptedEtherResponseIP4Addr(
DEF_DT_RX_RESPONSE_IP4_ADDR, DEF_DT_RX_RESPONSE_PORT,
DEF_RESPONSE_PROTOCOL, dt_ether_int_rx_ip4,
&dt_ether_int_rx_port, &dt_ether_int_protocol);
/* ... configure and start IDR ... */
}
}
}
ExitApp_DTEtherIDR(IDRcardIndex);
}
|
Important
|
Common connection errors you may encounter at this stage:
|
Program Structure
Entry Point
On standard platforms the entry point is main(). On VxWorks the entry point is DT_Ethernet_IDR():
#if defined (__VXWORKS__)
void DT_Ethernet_IDR(void)
#else
int32_t main(void)
#endif
Onboard vs. Offboard Interrupts
After prompting the user to select onboard or offboard interrupt processing, the sample configures the appropriate card index, board interface, and interrupt steering:
if (bProcessOnboardInterrupts == TRUE)
{
IDRcardIndex = userInput_DTInt.cardIndex;
boardInterface = NAI_INTF_ONBOARD;
steering = NAIBRD_INT_STEERING_ON_BOARD_1;
}
else /* Offboard */
{
IDRcardIndex = 0;
boardInterface = NAI_INTF_PCI;
steering = NAIBRD_INT_STEERING_CPCI_APP;
}
-
Onboard — the interrupt is handled by the board’s onboard processor, which executes the IDR command and sends the result over Ethernet. Use
NAIBRD_INT_STEERING_ON_BOARD_1for the IDR handler. -
Offboard — the interrupt is routed to the host via PCI/cPCI. Use
NAIBRD_INT_STEERING_CPCI_APP.
For Ethernet IDR, onboard steering is the typical configuration since the IDR mechanism relies on the onboard processor to execute the read commands and transmit the response.
Interrupt Configuration
The ConfigETHERInterrupt() function performs a five-step configuration sequence. This is the same sequence used in the standard DT_Interrupt sample, but with steering directed to the Ethernet IDR handler.
Step 1: Disable Interrupts
To prevent spurious interrupts during configuration, disable all DT interrupts first:
EnableDTInterrupt(FALSE);
EnableDTInterrupt() is a shared utility from DT_Interrupt_Common.c that enables or disables all four interrupt types across all channels.
Step 2: Clear Latched Status
Read and clear each latched status register to remove any stale interrupt conditions:
naibrd_DT_GetGroupStatusRaw(cardIndex, module, 1, NAI_DT_STATUS_BIT_LATCHED, &rawstatus);
naibrd_DT_ClearGroupStatusRaw(cardIndex, module, 1, NAI_DT_STATUS_BIT_LATCHED, rawstatus);
naibrd_DT_GetGroupStatusRaw(cardIndex, module, 1, NAI_DT_STATUS_LO_HI_TRANS_LATCHED, &rawstatus);
naibrd_DT_ClearGroupStatusRaw(cardIndex, module, 1, NAI_DT_STATUS_LO_HI_TRANS_LATCHED, rawstatus);
naibrd_DT_GetGroupStatusRaw(cardIndex, module, 1, NAI_DT_STATUS_HI_LO_TRANS_LATCHED, &rawstatus);
naibrd_DT_ClearGroupStatusRaw(cardIndex, module, 1, NAI_DT_STATUS_HI_LO_TRANS_LATCHED, rawstatus);
naibrd_DT_GetGroupStatusRaw(cardIndex, module, 1, NAI_DT_STATUS_OVERCURRENT_LATCHED, &rawstatus);
naibrd_DT_ClearGroupStatusRaw(cardIndex, module, 1, NAI_DT_STATUS_OVERCURRENT_LATCHED, rawstatus);
The pattern is: read the current latched status, then write the same value back to clear the bits that were set. This ensures a clean starting state.
Step 3: Set Interrupt Vectors
Map all four interrupt types to the same vector so a single IDR configuration can handle them all:
naibrd_DT_SetGroupInterruptVector(cardIndex, module, 1, NAI_DT_STATUS_BIT_LATCHED, NAI_DT_INTERRUPT_VECTOR);
naibrd_DT_SetGroupInterruptVector(cardIndex, module, 1, NAI_DT_STATUS_LO_HI_TRANS_LATCHED, NAI_DT_INTERRUPT_VECTOR);
naibrd_DT_SetGroupInterruptVector(cardIndex, module, 1, NAI_DT_STATUS_HI_LO_TRANS_LATCHED, NAI_DT_INTERRUPT_VECTOR);
naibrd_DT_SetGroupInterruptVector(cardIndex, module, 1, NAI_DT_STATUS_OVERCURRENT_LATCHED, NAI_DT_INTERRUPT_VECTOR);
-
NAI_DT_INTERRUPT_VECTOR— a constant defined innai_sys_int_common.h. All four status types share this vector.
Step 4: Set Edge/Level Trigger Mode
Configure whether interrupts trigger on edges (transitions) or levels (sustained conditions) for each channel:
for (chan = 1; chan <= boardState_DTInt.maxChannels; chan++)
{
naibrd_DT_SetEdgeLevelInterrupt(cardIndex, module, chan, NAI_DT_STATUS_BIT_LATCHED, interrupt_t);
naibrd_DT_SetEdgeLevelInterrupt(cardIndex, module, chan, NAI_DT_STATUS_LO_HI_TRANS_LATCHED, interrupt_t);
naibrd_DT_SetEdgeLevelInterrupt(cardIndex, module, chan, NAI_DT_STATUS_HI_LO_TRANS_LATCHED, interrupt_t);
naibrd_DT_SetEdgeLevelInterrupt(cardIndex, module, chan, NAI_DT_STATUS_OVERCURRENT_LATCHED, interrupt_t);
}
-
interrupt_t— the trigger type selected by the user (edge or level).
Step 5: Set Interrupt Steering
Direct all four interrupt types to the selected steering destination:
naibrd_DT_SetGroupInterruptSteering(cardIndex, module, 1, NAI_DT_STATUS_BIT_LATCHED, steering);
naibrd_DT_SetGroupInterruptSteering(cardIndex, module, 1, NAI_DT_STATUS_LO_HI_TRANS_LATCHED, steering);
naibrd_DT_SetGroupInterruptSteering(cardIndex, module, 1, NAI_DT_STATUS_HI_LO_TRANS_LATCHED, steering);
naibrd_DT_SetGroupInterruptSteering(cardIndex, module, 1, NAI_DT_STATUS_OVERCURRENT_LATCHED, steering);
For Ethernet IDR with onboard processing, steering is NAIBRD_INT_STEERING_ON_BOARD_1.
|
Important
|
Common Errors
|
Ethernet IDR Setup
Configure the IDR Command
The SetupDTEtherIDRconfig() function creates an Ethernet IDR configuration that tells the board what to do when an interrupt fires. The IDR command reads the four latched status registers (BIT, low-to-high transition, high-to-low transition, overcurrent) and sends the values to the host:
/* Clear any previous IDR configuration for this ID */
naibrd_Ether_ClearIDRConfig(IDRcardIndex, (uint16_t)DEF_ETHERNET_DT_IDR_ID);
/* Get the module's register base offset */
naibrd_GetModuleOffset(cardIndex, moduleNumber, &moduleOffset);
/* Build a ReadRegs command for the 4 latched status registers */
/* Starting at NAI_DT_GEN5_REG_BIT_LATCHED_STATUS_ADD with stride 0x10 */
MakeIDRDTboardReadRegsCommand(msgIndex, moduleOffset,
NAI_DT_GEN5_REG_BIT_LATCHED_STATUS_ADD,
DT_INTERRUPT_RESPONSE_REG_COUNT, stride, commands, ðcmdlen, boardInterface);
/* Set the complete IDR configuration */
naibrd_Ether_SetIDRConfig(IDRcardIndex, (uint16_t)DEF_ETHERNET_DT_IDR_ID,
protocol, DEF_RESPONSE_IP_LEN, dt_ether_int_rx_ip4,
dt_ether_int_rx_port, vector, cmdcount, cmdlength, commands);
-
DEF_ETHERNET_DT_IDR_ID— a unique identifier for this IDR configuration, defined innai_sys_int_common.h. -
DT_INTERRUPT_RESPONSE_REG_COUNT— 4 registers (BIT, Lo-Hi, Hi-Lo, Overcurrent). -
stride—0x10, the register offset between consecutive latched status registers. -
The
MakeIDRDTboardReadRegsCommand()helper callsnai_ether_MakeReadMessage()to build a Gen4 Ethernet read command.
Build the Ethernet Read Command
The MakeIDRDTboardReadRegsCommand() function constructs the raw Ethernet read message that will be embedded in the IDR:
void MakeIDRDTboardReadRegsCommand(uint16_t startIndex, uint32_t moduleOffset,
uint32_t regAddr, uint32_t count, uint32_t stride,
uint8_t commands[], uint16_t *cmdlen, int32_t boardInterface)
{
uint32_t regaddr = moduleOffset + regAddr;
msgIndex = (uint16_t)nai_ether_MakeReadMessage(&commands[startIndex],
seqno, NAI_ETHER_GEN4, (nai_intf_t)boardInterface,
regaddr, stride, count, NAI_REG32);
*cmdlen = msgIndex;
}
-
nai_ether_MakeReadMessage()builds a Gen4 Ethernet protocol read command that readscount32-bit registers starting atregaddrwith the specified stride.
Handling Interrupt Messages
When the board detects a DT interrupt, it executes the IDR command (reading the four latched status registers) and sends the results to the configured IP/port as an unprompted Ethernet response. The HandleDTEtherInterrupt() callback is invoked by the Ethernet message decoder when it receives a message with the DT IDR ID:
void HandleDTEtherInterrupt(uint16_t msglen, uint8_t msg[], uint16_t tdr_idr_id)
{
/* Decode the Ethernet message header */
offset = nai_ether_DecodeMessageHeader(msg, msglen, &seq, &tc, NAI_ETHER_GEN4, &size);
/* Extract the 4 status register values from the response */
switch (tc)
{
case NAI_ETHER_TYPECODE_RSP_COMMAND_COMPLETE_READ_4:
datacnt = (msglen - 10) / NAI_REG32;
for (i = 0; i < datacnt; i++)
{
data = (msg[offset] << 24) | (msg[offset+1] << 16) |
(msg[offset+2] << 8) | msg[offset+3];
offset += 4;
if (i < DT_INTERRUPT_RESPONSE_REG_COUNT)
dtstatus_int[i] = data;
}
break;
}
/* Process each status type */
for (i = 0; i < DT_INTERRUPT_RESPONSE_REG_COUNT; i++)
{
if (dtstatus_int[i] != 0)
{
/* Display the interrupt information */
/* Optionally prompt user before clearing */
/* Clear the status to re-arm the interrupt */
naibrd_DT_ClearGroupStatusRaw(cardIndex, moduleNumber, 1,
dt_status_type, dtstatus_int[i]);
}
}
}
The four status values in the response correspond to:
-
Index 0: BIT (fault) latched status
-
Index 1: Low-to-high transition latched status
-
Index 2: High-to-low transition latched status
-
Index 3: Overcurrent latched status
Each value is a bitmask where each bit represents one channel. A set bit indicates that channel experienced the corresponding interrupt condition.
After displaying the interrupt information, the handler clears the latched status with naibrd_DT_ClearGroupStatusRaw(). This is essential — clearing the status re-arms the interrupt so the next event will be detected. If the bPromptForInterruptClear flag is set, the handler waits for user confirmation before clearing.
Cleanup
On exit, the ExitApp_DTEtherIDR() function stops the IDR, terminates background threads, and closes the board connection:
void ExitApp_DTEtherIDR(int32_t IDRcardIndex)
{
ExitInterruptThreads();
if (userInput_DTInt.cardIndex >= 0)
{
naibrd_Ether_StopIDR(IDRcardIndex, (uint16_t)DEF_ETHERNET_DT_IDR_ID);
naibrd_Close(userInput_DTInt.cardIndex);
}
}
Always stop the IDR before closing the board connection. If the IDR is left running after the host disconnects, the board will continue sending UDP packets to the configured response address.
|
Important
|
Common Errors
|
Troubleshooting Reference
This table summarizes common errors and symptoms covered in the sections above. Consult your DT module’s manual for hardware-specific diagnostic procedures.
| Error / Symptom | Possible Causes | Suggested Resolution |
|---|---|---|
No board found or connection timeout |
Board not powered, Ethernet not connected, incorrect configuration file |
Verify hardware is powered and connected. If |
Gen4 Ethernet not supported |
Pre-Gen4 board hardware |
Use a Gen4 or later board. Ethernet IDR is not available on earlier hardware. |
No interrupt messages received |
Firewall blocking UDP, wrong response IP/port, interrupts not enabled, IDR not started |
Check firewall rules; verify the response IP and port match the host; confirm |
Interrupts fire once then stop |
Latched status not cleared in the handler |
Ensure |
Wrong status data in interrupt messages |
IDR command reads wrong registers; module offset incorrect |
Verify that |
IDR start fails |
IDR not configured before starting |
Call |
Module not present at selected slot |
No DT module installed, incorrect slot number |
Verify hardware configuration using the board menu. |
Wrong steering mode |
Using |
Set steering to |
Full Source
The complete source for this sample is provided below for reference. The sections above explain each part in detail.
Full Source — DT_Ethernet_IDR.c (SSK 1.x)
#if defined (WIN32)
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment (lib, "Ws2_32.lib")
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdint.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"
/* Generic NAI Board Library include files */
#include "nai.h"
#include "naibrd.h"
#include "naibrd_ether.h"
#include "advanced/nai_ether_adv.h"
/* Module Specific NAI Board Library files */
#include "functions/naibrd_dt.h"
#include "maps/nai_map_dt.h"
/* Module Specific sample code include files */
#include "DT_Interrupt_Common.h"
bool_t bDisplayEtherUPR;
etherIntFuncDef dtEtherIntFunc;
static const int8_t *DEF_CONFIG_FILE = (int8_t *)"default_DT_Ethernet_Int.txt";
/* Default Definitions for Ethernet IDR */
#define DEF_RESPONSE_IP_LEN ETHER_GEN4_IPv4_ADDR_LEN
#define DEF_RESPONSE_PROTOCOL ETHER_GEN4_UDP_PROTOCOL
uint16_t DEF_DT_RX_RESPONSE_PORT = 52802;
uint8_t DEF_DT_RX_RESPONSE_IP4_ADDR[] = {192,168,1,200};
uint16_t dt_ether_int_rx_port;
uint8_t dt_ether_int_rx_ip4[4];
uint16_t dt_ether_int_protocol;
#define DT_INTERRUPT_RESPONSE_REG_COUNT 4
bool_t bPromptForInterruptClear = TRUE;
/* Internal Function Prototypes */
bool_t SetupDTEtherIDRconfig(uint16_t protocol, int32_t IDRcardIndex, int32_t boardInterface);
void MakeIDRDTboardReadRegsCommand(uint16_t startIndex, uint32_t moduleOffset,
uint32_t regAddr, uint32_t count, uint32_t stride,
uint8_t commands[], uint16_t *cmdlen, int32_t boardInterface);
void HandleDTEtherInterrupt(uint16_t msglen, uint8_t msg[], uint16_t tdr_idr_id);
void StartDTEtherIDR(int32_t IDRcardIndex);
void ExitApp_DTEtherIDR(int32_t IDRcardIndex);
void ConfigETHERInterrupt(naibrd_int_steering_t steering);
#if defined (__VXWORKS__)
void DT_Ethernet_IDR(void)
#else
int32_t main(void)
#endif
{
bool_t bQuit = FALSE;
bool_t bSuccess = FALSE;
bool_t bProcessOnboardInterrupts = FALSE;
bool_t bPromptforInterruptClear = FALSE;
int32_t IDRcardIndex = 0;
naibrd_int_steering_t steering;
int32_t boardInterface;
DisplayMessage_DTInterrupt(MSG_BANNER_DT_INT);
if (naiapp_RunBoardMenu(DEF_CONFIG_FILE) == TRUE)
{
while (!bQuit)
{
naiapp_access_SystemSNModCfg();
bQuit = PromptUserInput_DTInterrupt(&bSuccess);
if ((!bQuit) & bSuccess)
{
bQuit = QueryUnpromptedEtherResponseIP4Addr(
DEF_DT_RX_RESPONSE_IP4_ADDR, DEF_DT_RX_RESPONSE_PORT,
DEF_RESPONSE_PROTOCOL, dt_ether_int_rx_ip4,
&dt_ether_int_rx_port, &dt_ether_int_protocol);
if (!bQuit)
bQuit = QueryUserForEtherIDRMsgDisplay(&bDisplayEtherUPR);
if (!bQuit)
bQuit = QueryUserForClearingInterruptPrompts(&bPromptforInterruptClear);
if (!bQuit)
bQuit = QueryUserForOnboardOffboardInterrupts(&bProcessOnboardInterrupts);
if (!bQuit)
{
if (bProcessOnboardInterrupts == TRUE)
{
IDRcardIndex = userInput_DTInt.cardIndex;
boardInterface = NAI_INTF_ONBOARD;
steering = NAIBRD_INT_STEERING_ON_BOARD_1;
}
else
{
IDRcardIndex = 0;
boardInterface = NAI_INTF_PCI;
steering = NAIBRD_INT_STEERING_CPCI_APP;
}
naiapp_utils_SetUPREtherPort(dt_ether_int_rx_port);
bSuccess = CheckNaiEtherProtocolVersion(userInput_DTInt.cardIndex);
if (bSuccess)
ConfigETHERInterrupt(steering);
else
printf("ERROR: Ethernet Interrupt Support Prior to Generation 4 Ethernet commands are currently NOT supported.\n");
if (bSuccess)
{
InitInterruptAppThread(ETHERNET_INT, dt_ether_int_protocol);
dtEtherIntFunc = HandleDTEtherInterrupt;
if (SetupDTEtherIDRconfig(dt_ether_int_protocol, IDRcardIndex, boardInterface))
{
DisplayMessage_DTInterrupt(MSG_USER_TRIGGER_DT_INT);
StartDTEtherIDR(IDRcardIndex);
EnableDTInterrupt(TRUE);
UpdateThreadState(RUN);
}
else
bQuit = TRUE;
}
}
else
{
UpdateThreadState(TERMINATED);
}
bQuit = TRUE;
}
}
}
ExitApp_DTEtherIDR(IDRcardIndex);
#if !defined (__VXWORKS__)
return(0);
#endif
}
/* ... remaining functions: SetupDTEtherIDRconfig, StartDTEtherIDR, */
/* HandleDTEtherInterrupt, ExitApp_DTEtherIDR, */
/* MakeIDRDTboardReadRegsCommand, ConfigETHERInterrupt */
/* See the SSK installation directory for the complete source. */