CTS DIF Ethernet
Edit this on GitLab
CTS DIF Ethernet Sample Application (SSK 1.x)
Overview
The CTS DIF Ethernet sample application demonstrates how to perform Coordinated Time Sequencing (CTS) operations on DIF (Differential Digital I/O) modules over an Ethernet connection using the NAI Software Support Kit (SSK 1.x). CTS allows DIF modules to drive discrete outputs at precisely timed intervals controlled by a coarse/subsecond counter system and a FIFO event queue. This sample covers the full CTS workflow: setting default output values on flush, configuring coarse and subsecond counters, loading timed events into the FIFO, flushing queues, initiating the clock, filling the FIFO from a file, and running a multi-module synchronized CTS demonstration.
This sample supports the DF3 module type. It communicates with the board over Ethernet, and the interactive menu lets you exercise each CTS operation individually. It serves as a practical API reference — each menu command maps directly to one or more naibrd_DIF_CTS_*() API calls that you can lift into your own code.
Prerequisites
Before running this sample, make sure you have:
-
An NAI board with one or two DIF (DF3) modules installed, connected via Ethernet.
-
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.
-
For the Demo command: an event data file (
eventdata.txt) in the working directory containing timed events in CSV format (coarse counter, subsecond counter, output value per line).
How to Run
Launch the CTS_DIF_Ethernet executable from your build output directory. On startup the application looks for a configuration file (default_CTS_DIF_Ethernet.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 and a DIF module is selected, the CTS operation menu appears.
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 DIF. |
The main() function follows a standard SSK 1.x startup flow:
-
Call
naiapp_RunBoardMenu()to load a saved configuration file (if one exists) or present the interactive board menu. The configuration file (default_CTS_DIF_Ethernet.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. -
Query the user for a card index with
naiapp_query_CardIndex(). -
Query for a module slot with
naiapp_query_ModuleNumber(). -
Retrieve the module ID with
naibrd_GetModuleID()so downstream code can verify the module is a DIF type.
#if defined (__VXWORKS__)
int32_t DIF_CTS(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_DIF_CTS(cardIndex, module, moduleID);
}
}
}
/* ... quit/restart prompt ... */
}
}
naiapp_access_CloseAllOpenCards();
return 0;
}
The Run_DIF_CTS() function checks whether the selected module is actually a DIF type by calling naibrd_DIF_GetChannelCount(). If the channel count is zero, the module is not a DIF and the sample prints an error. Otherwise it enters the CTS command loop via Cfg_DIF_CTS().
|
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 DIF_CTS() — the SSK 1.x build system selects the correct variant via a preprocessor guard:
#if defined (__VXWORKS__)
int32_t DIF_CTS(void)
#else
int32_t main(void)
#endif
Application Parameters
The Cfg_DIF_CTS() function populates an naiapp_AppParameters_t struct that is passed to each command handler. Your application will need to track these same values:
cts_eth_params->cardIndex = cardIndex;
cts_eth_params->module = module;
cts_eth_params->channel = 0;
-
cardIndex— identifies which board in a multi-board system. -
module— the slot number where the DIF module is installed. -
channel— not used for CTS operations (CTS operates on the entire module, not individual channels).
Command Loop
Cfg_DIF_CTS() drives the interactive command loop. On each iteration it displays the current CTS configuration, prints the command menu, and dispatches the user’s selection:
| Command | Description |
|---|---|
Default |
Set the default output value driven when queues are flushed |
Outputs |
Set all channels to output mode |
Flush |
Flush event queues (drives default output immediately) |
Init |
Initiate the CTS clock |
Course |
Set the coarse counter value |
Subsec |
Set the subsecond counter value |
Load |
Load a single timed event into the FIFO |
Fill |
Fill the FIFO from an event data file and initiate the clock |
Demo |
Run a full multi-module CTS demonstration sequence |
The menu-driven structure is a convenience of the sample application. In your own application, you would call the same underlying naibrd_DIF_CTS_*() API functions directly.
CTS Configuration Display
On each command loop iteration, Display_DIF_CTS_Cfg() reads and displays the current CTS state. To read the same information in your own application:
uint32_t outputState, courseCount, subsecCount, defaultVal;
naibrd_DIF_GetGroupInputStateRaw(cardIndex, module, 1, &outputState);
naibrd_DIF_CTS_GetCourseCounter(cardIndex, module, &courseCount);
naibrd_DIF_CTS_GetSubsecCounter(cardIndex, module, &subsecCount);
naibrd_DIF_CTS_GetSwitchDefVal(cardIndex, module, &defaultVal);
-
outputState— the current I/O state of all channels as a bitmask. Each bit represents one channel. -
courseCount— the current value of the coarse counter (high-order timing). -
subsecCount— the current value of the subsecond counter (fine-grained timing within each coarse tick). -
defaultVal— the output bit pattern driven when queues are flushed.
The display also calls naibrd_GetModuleInfo() to retrieve the module ID, version, and revision for identification.
CTS Counter and Queue Operations
This section covers the API calls used to configure the CTS timing system: setting coarse and subsecond counters, managing the default output value, flushing event queues, and initiating the clock.
Set Default Output Value
The default output value is the bit pattern driven to the discrete outputs when the event queues are flushed (emptied). To set this value in your own application, call naibrd_DIF_CTS_SetSwitchDefVal():
uint32_t defaultVal = 0x0000FFFF;
naibrd_DIF_CTS_SetSwitchDefVal(cardIndex, module, defaultVal);
-
cardIndex— identifies the board. -
module— the slot containing the DIF module. -
defaultVal— the output bit pattern. Each bit corresponds to one discrete output channel.
The sample validates the module ID (must be NAI_MODULE_ID_DF3) before calling this function. On unsupported modules, the call is skipped.
Set All Channels to Output Mode
Before driving outputs, you must configure the DIF channels as outputs. The sample writes directly to the I/O direction register:
naibrd_WriteReg32(cardIndex, module, 0x00001038, 0x0000FFFF);
-
The register address
0x00001038is the I/O direction register for the DIF module. Setting a bit to 1 configures that channel as an output. -
The value
0x0000FFFFsets all 16 channels to output mode.
|
Note
|
The register address and value depend on the specific DIF module variant. Consult your module’s manual for the correct I/O direction register. |
Flush Event Queues
To clear all pending events from the FIFO and drive the default output value to the pins, call naibrd_DIF_CTS_FlushQueues():
naibrd_DIF_CTS_FlushQueues(cardIndex, module);
Flushing discards all queued events and immediately drives the default output value (set by naibrd_DIF_CTS_SetSwitchDefVal()) to the output pins. This is useful for resetting the CTS system to a known state before loading a new event sequence.
Set Coarse Counter
The coarse counter provides the high-order timing reference for CTS events. Each coarse tick typically represents one second. To set the coarse counter in your own application:
uint32_t courseCount = 0x00001000;
naibrd_DIF_CTS_SetCourseCounter(cardIndex, module, courseCount);
-
cardIndex— identifies the board. -
module— the slot containing the DIF module. -
courseCount— the coarse counter value to load.
Set Subsecond Counter
The subsecond counter provides fine-grained timing within each coarse tick. To set the subsecond counter:
uint32_t subsecCount = 0x00000100;
naibrd_DIF_CTS_SetSubsecCounter(cardIndex, module, subsecCount);
-
cardIndex— identifies the board. -
module— the slot containing the DIF module. -
subsecCount— the subsecond counter value to load.
Initiate the CTS Clock
To start the CTS clock, which begins executing queued events when the counters reach the specified times:
naibrd_DIF_CTS_ClockInitiate(cardIndex, module);
After initiating the clock, the module begins incrementing its coarse and subsecond counters. When the counters match a queued event’s timestamp, the module drives the event’s output value to the pins.
|
Important
|
Common Errors
|
Loading Events
Load a Single Event
To load a single timed event specifying when and what output values to drive, call naibrd_DIF_CTS_WriteEvent():
uint32_t courseCount = 0x00001000;
uint32_t subsecCount = 0x00000000;
uint32_t eventData = 0x0000FFFF;
naibrd_DIF_CTS_WriteEvent(cardIndex, module, courseCount, subsecCount, eventData);
-
cardIndex— identifies the board. -
module— the slot containing the DIF module. -
courseCount— the coarse counter value at which this event should fire. -
subsecCount— the subsecond counter value at which this event should fire. -
eventData— the output bit pattern to drive when the event fires.
Events are loaded into a FIFO and execute in sequence when the CTS clock reaches the specified time.
Fill FIFO from Event Data File
The Fill command reads events from a user-specified file and loads them into the FIFO, then initiates the clock. The load_DIF_FIFO() function opens the file, parses each line using fill_Event_Struct(), and calls naibrd_DIF_CTS_WriteEvent() for each event.
The event data file is a CSV file where each line contains three hexadecimal values separated by commas: coarse counter, subsecond counter, and output value. For example:
0x00000900,0x00000000,0x000000FF 0x00000901,0x00000000,0x0000FF00 0x00000902,0x00000000,0x00000000
After loading all events, the sample calls naibrd_DIF_CTS_ClockInitiate() to begin event execution.
Synchronized Multi-Module Demo
The Demo command (run_DIF_CTS_demo()) demonstrates a complete multi-module CTS workflow using two DIF modules in slots 2 and 3 (base and sync modules). The sequence:
-
Verify both module slots contain DIF modules by checking
naibrd_DIF_GetChannelCount(). -
Set initial coarse counters on both modules to a value well outside the event range (
0x00004000). -
Call
init_DIF_System()to configure both modules: set all channels as outputs, flush queues, initiate the clock, and load events from the event data file. -
Clear any leftover event interrupts with
naibrd_DIF_ClearGroupStatusRaw(). -
Set both modules' coarse counters to a value just before the first event (
0x000008F3). -
Monitor the latched status register for event detection. When an event fires, the
EVENT_DETECTEDbit (0x00000040) is set. -
On each detected event, read back the I/O state with
naibrd_DIF_GetGroupInputStateRaw(), reverse-map the channel values, and compare against expected values.
/* Check for event detection */
naibrd_ReadReg32(cardIndex, baseModule, 0x00000844, &moduleSlotStatus);
if ((moduleSlotStatus & EVENT_DETECTED) == EVENT_DETECTED)
{
/* Clear the latch */
naibrd_WriteReg32(cardIndex, baseModule, 0x00000844, EVENT_DETECTED);
/* Read back the I/O state */
naibrd_DIF_GetGroupInputStateRaw(cardIndex, baseModule, 1, &readBackValue);
/* Read counters for verification */
naibrd_DIF_CTS_GetCourseCounter(cardIndex, baseModule, &courseCounter);
naibrd_DIF_CTS_GetSubsecCounter(cardIndex, baseModule, &subsecCounter);
}
Channel-to-Bit Mapping
When using two DIF modules to drive a combined output, the sample maps logical event bit positions to physical channel numbers using map_DF3_EventValue(). The mapping accounts for hardware wiring differences between module slots. The reverse mapping (reverseMap_DF3_EventValue()) translates read-back values to the original logical representation for comparison.
In your own application, you would define a mapping appropriate to your wiring configuration, or pass event data directly if logical and physical bit positions already match.
|
Important
|
Common Errors
|
Troubleshooting Reference
This table summarizes common errors and symptoms covered in the sections above. Consult your 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 |
Module not recognized as DIF |
Selected slot does not contain a DF3 module |
Verify DIF module installation and slot number |
Events do not fire |
CTS clock not initiated after loading events |
Call |
Outputs stay at default value |
Channels not configured as outputs |
Write the I/O direction register before loading events |
Counter values incorrect after Demo |
Counters not set before clock initiation, or event file has unexpected values |
Set counters before initiating; verify event file format |
Unsupported function for this module |
Module is not a DF3; CTS operations only work on DF3 modules |
Select a slot containing a DF3 module |
FIFO overflow |
Too many events loaded without initiating the clock |
Flush queues and reload a smaller event set, or initiate the clock to begin consuming events |
Demo reports FAILURE |
Detected event value or counter mismatch with expected values |
Check event data file, verify both modules are in slots 2 and 3, verify channel mapping |
Full Source
The complete source for this sample is provided below for reference. The sections above explain each part in detail.
Full Source — CTS_DIF_Ethernet.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_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_dif.h"
#include "functions/naibrd_dif_cts.h"
#include "advanced/nai_ether_adv.h"
#include "boards/naibrd_gen5.h"
static const int8_t *CONFIG_FILE = "default_CTS_DIF_Ethernet.txt";
static const uint32_t EVENT_DETECTED = 0x00000040;
/****** Command Table *******/
enum dif_cts_commands
{
DIF_CTS_CMD_DEF,
DIF_CTS_CMD_OUTPUT,
DIF_CTS_CMD_FLUSH,
DIF_CTS_CMD_INIT,
DIF_CTS_CMD_COURSE,
DIF_CTS_CMD_SUBSEC,
DIF_CTS_CMD_LOAD,
DIF_CTS_CMD_FILL,
DIF_CTS_CMD_DEMO,
DIF_CTS_CMD_COUNT
};
struct _EventStruct
{
int32_t courseCounter;
int32_t subSecCounter;
int32_t value;
};
#define MAX_EVENTS 5000
static struct _EventStruct g_firstDF3Events[MAX_EVENTS];
static struct _EventStruct g_secondDF3Events[MAX_EVENTS];
/* Function prototypes */
int32_t Run_DIF_CTS(int32_t cardIndex, int32_t module, uint32_t moduleID);
static void Cfg_DIF_CTS(int32_t cardIndex, int32_t module, uint32_t ModuleID);
void Display_DIF_CTS_Cfg(int32_t cardIndex, int32_t module, uint32_t ModuleID);
nai_status_t Configure_DIF_CTS_Default(int32_t paramCount, int32_t* p_params);
nai_status_t Configure_DIF_CTS_Course(int32_t paramCount, int32_t* p_params);
nai_status_t Configure_DIF_CTS_Subsec(int32_t paramCount, int32_t* p_params);
nai_status_t Load_DIF_CTS_Event(int32_t paramCount, int32_t* p_params);
void run_DIF_CTS_demo(int32_t cardIndex);
bool_t load_DIF_FIFO(int32_t cardIndex, int32_t DIFModuleSlot);
bool_t init_DIF_System(int32_t cardIndex, int32_t DIFModuleSlot1, int32_t DIFModuleSlot2, char const* DIFEventDataFile);
static void fill_Event_Struct(char* line, struct _EventStruct *eventStruct);
static int32_t reverseMap_DF3_EventValue(int32_t DF3ModuleSlot, int32_t nEventValue);
static int32_t map_DF3_EventValue(int32_t DF3ModuleSlot, int32_t nEventValue);
/****** Command Tables *******/
naiapp_cmdtbl_params_t DIF_CTSMenuCmds[] = {
{"Default", "DIF Set Default Output on Flush", DIF_CTS_CMD_DEF, Configure_DIF_CTS_Default},
{"Outputs", "DIF Set All Output Mode", DIF_CTS_CMD_OUTPUT, NULL},
{"Flush", "DIF Flush Queues", DIF_CTS_CMD_FLUSH, NULL},
{"Init", "DIF Clock Initiate", DIF_CTS_CMD_INIT, NULL},
{"Course", "DIF Set Course Counter", DIF_CTS_CMD_COURSE, Configure_DIF_CTS_Course},
{"Subsec", "DIF Set Subsec Counter", DIF_CTS_CMD_SUBSEC, Configure_DIF_CTS_Subsec},
{"Load", "DIF Load Event", DIF_CTS_CMD_LOAD, Load_DIF_CTS_Event},
{"Fill", "DIF Fill FIFO", DIF_CTS_CMD_FILL, NULL},
{"Demo", "DIF Run Demo", DIF_CTS_CMD_DEMO, NULL},
};
/* Global Variables */
static uint32_t g_TotalEventCount = 0;
static int8_t g_SilentMode = 0;
static int8_t g_InitCounters = 0;
#if defined (__VXWORKS__)
int32_t DIF_CTS(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_DIF_CTS(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;
}
int32_t Run_DIF_CTS(int32_t cardIndex, int32_t module, uint32_t moduleID)
{
int32_t MaxChannel;
MaxChannel = naibrd_DIF_GetChannelCount(moduleID);
if (MaxChannel == 0)
printf(" *** Module selection not recognized as DIF module. ***\n\n");
else
Cfg_DIF_CTS(cardIndex, module, moduleID);
return cardIndex;
}
/* ... remaining functions: Cfg_DIF_CTS, Display_DIF_CTS_Cfg, */
/* Configure_DIF_CTS_Default, Configure_DIF_CTS_Course, Configure_DIF_CTS_Subsec, */
/* Load_DIF_CTS_Event, run_DIF_CTS_demo, init_DIF_System, load_DIF_FIFO, */
/* fill_Event_Struct, map_DF3_EventValue, reverseMap_DF3_EventValue */
/* See the SSK installation directory for the complete source. */