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

SER HDLC LOOPBACK

SER HDLC Loopback Sample Application (SSK 1.x)

Overview

The SER HDLC Loopback sample application demonstrates how to configure a serial channel for HDLC synchronous communication and run a loopback test using the NAI Software Support Kit (SSK 1.x). HDLC is a frame-oriented protocol — the hardware automatically wraps transmitted data with sync characters and a CRC, so your application works with payload bytes rather than raw framing details.

The application supports two loopback paths:

  • Gen2/3 modules (internal loopback) — a single channel is placed in loopback mode at 115,200 bps. No external wiring is needed.

  • Gen5 modules (external loopback) — two channels on the same module are connected through a physical wrap cable at 1 Mbps. The user selects a Tx channel and an Rx channel, then wires them together.

Note
For async serial loopback, see the SER ASync Loopback guide. This guide covers the synchronous HDLC protocol.

Supported Modules

  • Gen2/3 (internal loopback): P8, PC, PD, Px, KB

  • Gen5 (external loopback): SC5 and other Gen5 serial modules

  • Combination modules: CMH

SC3 does not support synchronous serial communications. The application detects SC3 modules by FPGA revision and rejects them at startup.

Prerequisites

Before running this sample, make sure you have:

  • An NAI board with a supported SER module installed (see list above).

  • 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 Gen5 external loopback: a wrap cable connecting the Tx channel to the Rx channel (Tx+ to Rx+, Tx- to Rx-, Clk+ to Clk+, Clk- to Clk-).

How to Run

Launch the SER_HDLC_LOOPBACK executable from your build output directory. On startup the application looks for a configuration file (default_SerHDLC_LOOPBACK.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 queries for a channel (Gen2/3) or a Tx/Rx channel pair (Gen5) and runs the loopback test.

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 SER.

The main() function follows a standard SSK 1.x startup flow:

  1. Call naiapp_RunBoardMenu() to load a saved configuration file (if one exists) or present the interactive board menu. The configuration file (default_SerHDLC_LOOPBACK.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.

  2. Query the user for a card index with naiapp_query_CardIndex().

  3. Query for a module slot with naiapp_query_ModuleNumber().

  4. Retrieve the module ID with naibrd_GetModuleID() and the FPGA revision with naibrd_GetModuleRev() so downstream code can adapt to the specific SER variant installed.

  5. Validate the module — if the module is an SC3 with an FPGA revision below NAI_SC3_REV_1_0, the application prints an error and returns to the menu because SC3 does not support synchronous serial (line 86).

#if defined (__VXWORKS__)
int32_t SER_HDLC_Sample(void)
#else
int32_t main(void)
#endif
{
   bool_t stop = FALSE;
   int32_t cardIndex;
   int32_t moduleCnt;
   int32_t module;
   uint32_t moduleID = 0;
   uint32_t fpgaRev, modProcRev;
   int8_t inputBuffer[80];
   int32_t inputResponseCnt;

   if (naiapp_RunBoardMenu(CONFIG_FILE) == (bool_t)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)
            {
               /* Check to make sure the module is NOT an SC3 */
               moduleID = naibrd_GetModuleID(cardIndex, module);
               check_status(naibrd_GetModuleRev(cardIndex, module, &modProcRev, &fpgaRev));
               if (moduleID == 0)
               {
                  printf("\nInvalid Module\n");
               }
               else if ((moduleID == NAI_MODULE_ID_SC3) && (fpgaRev < NAI_SC3_REV_1_0))
               {
                  printf("\nSC3 does not support synchronous serial communications.\n");
               }
               else
               {
                  Run_SER_HDLC_LOOPBACK(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:

  • 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 SER module. Use the board menu to verify which slots are populated.

Program Structure

The application entry point is main() on most platforms, or SER_HDLC_Sample() on VxWorks. After the board connection is established, main() validates the selected module and calls Run_SER_HDLC_LOOPBACK() to execute the loopback test.

Module Validation

Before running the test, main() checks the module ID and FPGA revision (line 86). SC3 modules with an FPGA revision below NAI_SC3_REV_1_0 are rejected because SC3 does not support synchronous serial communications. All other recognized SER modules pass validation and proceed to the loopback routine.

Gen2/3 vs. Gen5 Channel Selection

The loopback routine branches based on the module ID:

  • Gen2/3 modules (P8, PC, PD, Px, KB) — the user selects a single channel. That channel is placed in internal loopback mode, so it transmits to itself without any external cabling.

  • Gen5 modules (all other supported modules) — the user selects a Tx channel and an Rx channel separately. These two channels must be physically connected with a wrap cable for the test to work.

Linear Flow

Regardless of generation, the test follows a straight-line sequence:

  1. Configure — reset the channel(s), clear FIFOs, set HDLC protocol, interface level, clock mode, and baud rate.

  2. Send — load incremental test data into the Tx FIFO and initiate transmission.

  3. Receive — read back data from the Rx FIFO (or HDLC packet buffer on Gen2/3).

  4. Verify — print each sent/received byte pair with its status flags so the user can confirm the data matches.

Hardware-Managed HDLC Framing

What HDLC Framing Is

HDLC (High-Level Data Link Control) is a frame-oriented synchronous protocol. Sync characters mark frame boundaries, a CRC field ensures data integrity, and an address field allows selective reception. Unlike async serial — where each byte is sent individually with start and stop bits — HDLC groups bytes into frames that travel as a unit.

What the Hardware Does Automatically

The SER module handles all framing mechanics in hardware. On transmit, it adds sync characters at the start of the frame, computes the CRC over the payload, and appends the CRC at the end. On receive, it detects frame boundaries (beginning-of-frame and end-of-frame), validates the CRC, and presents the payload to the application with per-byte status information. The application never constructs or parses HDLC frames directly.

What the Application Does

To transmit, the application loads raw data bytes into the Tx FIFO via naibrd_SER_TransmitBuffer() and triggers transmission with TransmitInitiate(). The hardware wraps those bytes in a complete HDLC frame automatically.

On receive, the application reads 32-bit words from the Rx buffer. Each word contains the data byte in the lower byte and a status byte in the upper byte. The status byte tells the application where frame boundaries fall and whether any errors were detected.

Status Byte Decoding

Each received 32-bit word encodes data and status as follows:

  • Data byte: word & 0x00FF

  • Status byte: (word >> 8) & 0x00FF

Status flag values:

  • NAI_SER_SYNC_BOF (0x40) — beginning of frame

  • NAI_SER_SYNC_EOF (0x02) — end of frame

  • NAI_SER_SYNC_ER0 (0x04) — error code 0

  • NAI_SER_SYNC_ER1 (0x08) — error code 1

  • NAI_SER_SYNC_ER2 (0x10) — error code 2

  • 0x00 — normal data byte within frame

Contrast with Async Serial

Async serial has no framing — bytes are sent individually, each wrapped in start and stop bits with optional parity for single-byte error detection. HDLC groups bytes into frames with guaranteed integrity via CRC. There is no need to configure parity, data bits, or stop bits because HDLC handles framing internally.

FIFO Setup

Before configuring protocol parameters, the application resets each channel and clears its Rx and Tx FIFOs. The clear operation is asynchronous — the hardware sets control-register bits while the clear is in progress — so the application must poll until those bits drop or a timeout expires.

The sequence for each channel is:

  1. Call naibrd_SER_ChannelReset() to reset the channel.

  2. Call naibrd_SER_ClearRxFifo() and naibrd_SER_ClearTxFifo() to request FIFO clears.

  3. Poll naibrd_SER_GetChannelControlRaw() in a loop, checking NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO in the low 16 bits. The loop sleeps 1 ms per iteration (nai_msDelay(1)) and gives up after 1,000 iterations (a 1-second timeout).

Gen2/3 — Single Channel

Gen2/3 modules use a single channel for internal loopback, so one reset/clear/poll cycle is sufficient:

check_status(naibrd_SER_ChannelReset(cardIndex, module, chanNum));
check_status(naibrd_SER_ClearRxFifo(cardIndex, module, chanNum));
check_status(naibrd_SER_ClearTxFifo(cardIndex, module, chanNum));
nCntlValueLo = NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO;
for (i = 0; i < 1000 && (nCntlValueLo & (NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO)); i++)
{
   nai_ser_chanctrl chanCtrlRaw;
   check_status(naibrd_SER_GetChannelControlRaw(cardIndex, module, chanNum, &chanCtrlRaw));
   nCntlValueLo = chanCtrlRaw & 0x0000FFFF;
   nai_msDelay(1);
}
if (i == 1000)
{
   printf("Unable to clear FIFOs %d\n", chanNum);
   printf("Please press Enter to exit...");
   while ((ch = getchar()) != 0x0A);
   return;
}

Gen5 — Tx and Rx Channels

Gen5 modules use two separate channels connected by a wrap cable. Both the Tx channel and the Rx channel must be reset and cleared independently — each with its own polling loop:

/* Reset and clear Transmitter's FIFOs. */
check_status(naibrd_SER_ChannelReset(cardIndex, module, txChanNum));
check_status(naibrd_SER_ClearRxFifo(cardIndex, module, txChanNum));
check_status(naibrd_SER_ClearTxFifo(cardIndex, module, txChanNum));
nCntlValueLo = NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO;
for (i = 0; i < 1000 && (nCntlValueLo & (NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO)); i++)
{
   nai_ser_chanctrl chanCtrlRaw;
   check_status(naibrd_SER_GetChannelControlRaw(cardIndex, module, txChanNum, &chanCtrlRaw));
   nCntlValueLo = chanCtrlRaw & 0x0000FFFF;
   nai_msDelay(1);
}
if (i == 1000)
{
   printf("Unable to clear Transmitter FIFOs %d\n", txChanNum);
   printf("Please press Enter to exit...");
   while ((ch = getchar()) != 0x0A);
   return;
}

/* Reset and clear Receiver's FIFOs. */
check_status(naibrd_SER_ChannelReset(cardIndex, module, rxChanNum));
check_status(naibrd_SER_ClearRxFifo(cardIndex, module, rxChanNum));
check_status(naibrd_SER_ClearTxFifo(cardIndex, module, rxChanNum));
nCntlValueLo = NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO;
for (i = 0; i < 1000 && (nCntlValueLo & (NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO)); i++)
{
   nai_ser_chanctrl chanCtrlRaw;
   check_status(naibrd_SER_GetChannelControlRaw(cardIndex, module, rxChanNum, &chanCtrlRaw));
   nCntlValueLo = chanCtrlRaw & 0x0000FFFF;
   nai_msDelay(1);
}
if (i == 1000)
{
   printf("Unable to clear Receiver FIFOs %d\n", rxChanNum);
   printf("Please press Enter to exit...");
   while ((ch = getchar()) != 0x0A);
   return;
}
Important
  • FIFO clear timeout — if the polling loop reaches 1,000 iterations without the clear bits dropping, the channel may be in use or the hardware is not responding. The application prints an error and returns. Check that the module is seated correctly and that no other process holds the channel open.

  • Gen5: forgetting to clear the second channel’s FIFOs — Gen5 external loopback uses two independent channels. Both the Tx channel and the Rx channel must be reset and cleared separately. Skipping either one leaves stale data in the FIFOs and will corrupt the loopback test.

Channel Configuration

After the FIFOs are cleared, the application configures each channel for HDLC communication. Both Gen2/3 and Gen5 paths share the same core calls — naibrd_SER_SetProtocol(NAI_SER_PROTOCOL_HDLC) selects HDLC mode (protocol code 0x0003), naibrd_SER_SetBaudrate() sets the line rate, and naibrd_SER_SetReceiverEnable() arms the receiver. The receiver must be enabled after all other parameters are in place so the hardware does not start clocking data before the configuration is complete.

The differences between generations are interface level, clock sourcing, sync-character setup, and baud rate. The subsections below detail each path.

Gen2/3 — Single-Channel Internal Loopback

check_status(naibrd_SER_SetProtocol(cardIndex, module, chanNum, NAI_SER_PROTOCOL_HDLC));        /* HDLC mode */
check_status(naibrd_SER_SetInterfaceLevel(cardIndex, module, chanNum, NAI_SER_INTF_LOOPBACK));  /* LoopBack */
check_status(naibrd_SER_SetClockMode(cardIndex, module, chanNum, TXINT_RXINT));                 /* Tx and Rx internal */
check_status(naibrd_SER_SetBaudrate(cardIndex, module, chanNum, 115200));                       /* 115,200 HDLC/sync mode */
check_status(naibrd_SER_SetRxHdlcAddrsSyncChar(cardIndex, module, chanNum, 0x247A));            /* recv sync char - must be same as xmit for loopback */
check_status(naibrd_SER_SetTxHdlcAddrsSyncChar(cardIndex, module, chanNum, 0x247A));            /* xmit sync char - must be same as recv for loopback */
nConfigWordLo = (NAI_SER_CFGLO_ADDR_LEN_16 | NAI_SER_CFGLO_ADDR_REC);                           /* 16-bit HDLC addr recognition */
check_status(naibrd_SER_SetChannelConfigRaw(cardIndex, module, chanNum, nConfigWordLo));

nai_msDelay(20);     /* Allow 20ms for the HW to acknowledge the configuration (GEN 2/3 only)*/

check_status(naibrd_SER_SetReceiverEnable(cardIndex, module, chanNum, 1));                      /* Enable the Receiver */

/* 100 millisecond wait to ensure the receiver is on. */
nai_msDelay(100);

The calls break down as follows:

  • SetInterfaceLevel(NAI_SER_INTF_LOOPBACK) — places the channel in internal loopback mode. Transmitted data is routed back to the receiver inside the module; no external wiring is required.

  • SetClockMode(TXINT_RXINT) — both the transmit clock and receive clock are generated internally by the module.

  • SetBaudrate(115200) — sets the line rate to 115,200 bps.

  • SetRxHdlcAddrsSyncChar(0x247A) — programs the receive sync character used for frame detection.

  • SetTxHdlcAddrsSyncChar(0x247A) — programs the transmit sync character. This must match the Rx sync character for loopback to work. If the values differ, the receiver will never detect a valid frame boundary.

  • SetChannelConfigRaw(NAI_SER_CFGLO_ADDR_LEN_16 | NAI_SER_CFGLO_ADDR_REC) — enables 16-bit HDLC address recognition.

  • nai_msDelay(20) — a 20 ms delay that gives the Gen2/3 hardware time to acknowledge the configuration writes.

  • SetReceiverEnable(1) — arms the receiver. Called last so that all parameters are in place before clocking begins.

  • nai_msDelay(100) — a 100 ms stabilization wait to ensure the receiver is fully active before the application begins transmitting.

Gen5 — Two-Channel External Loopback

Note

Gen5 modules do not support internal HDLC loopback. You must physically connect the Tx channel’s output to the Rx channel’s input using RS-422 wiring:

Tx Channel   |   Rx Channel
  Tx+       -->    Rx+
  Tx-       -->    Rx-
  Clk+      -->    Clk+
  Clk-      -->    Clk-

Consult your board’s manual for the correct connector pin assignments.

/* Configure Transmitter. */
printf("\nTransmitter Serial Channel # %d\n", txChanNum);
check_status(naibrd_SER_SetProtocol(cardIndex, module, txChanNum, NAI_SER_PROTOCOL_HDLC));            /* HDLC mode */
check_status(naibrd_SER_SetInterfaceLevel(cardIndex, module, txChanNum, NAI_SER_GEN5_INTF_RS422));    /* RS422 */
check_status(naibrd_SER_SetClockMode(cardIndex, module, txChanNum, TXEXT_RXEXT));                     /* External clock (receiving clock input). */
check_status(naibrd_SER_SetBaudrate(cardIndex, module, txChanNum, 1000000));                          /* 1Mbps */

/* Configure Receiver. */
printf("Receiver Serial Channel # %d\n", rxChanNum);
check_status(naibrd_SER_SetProtocol(cardIndex, module, rxChanNum, NAI_SER_PROTOCOL_HDLC));          /* HDLC mode */
check_status(naibrd_SER_SetInterfaceLevel(cardIndex, module, rxChanNum, NAI_SER_GEN5_INTF_RS422));  /* RS422 */
check_status(naibrd_SER_SetClockMode(cardIndex, module, rxChanNum, TXINT_RXINT));                   /* Internal clock (driving clock). */
check_status(naibrd_SER_SetBaudrate(cardIndex, module, rxChanNum, 1000000));                        /* 1Mbps*/
check_status(naibrd_SER_SetReceiverEnable(cardIndex, module, rxChanNum, 1));                        /* Enable the Receiver */

Gen5 configures two independent channels — a Tx channel and an Rx channel — connected by a physical RS-422 wrap cable:

  • Tx channel — SetProtocol(HDLC), SetInterfaceLevel(NAI_SER_GEN5_INTF_RS422), SetClockMode(TXEXT_RXEXT), SetBaudrate(1000000). The Tx channel is set to external clock mode (TXEXT_RXEXT), meaning it receives its clock from the wrap cable rather than generating one.

  • Rx channel — SetProtocol(HDLC), SetInterfaceLevel(NAI_SER_GEN5_INTF_RS422), SetClockMode(TXINT_RXINT), SetBaudrate(1000000), SetReceiverEnable(1). The Rx channel is set to internal clock mode (TXINT_RXINT), meaning it drives the clock on its output pins.

The clock arrangement is deliberate: the Rx channel generates the clock and drives it out on the Clk+/Clk- pins. The wrap cable carries that clock to the Tx channel’s Clk+/Clk- input. This establishes a shared clock reference for the external loopback — the Rx channel is the clock master and the Tx channel is the clock slave. Both channels run at 1 Mbps. Only the Rx channel calls SetReceiverEnable() because only the Rx channel needs to listen for incoming frames.

Important
  • Sync character mismatch (Gen2/3) — the Rx and Tx sync characters must be identical for loopback. If SetRxHdlcAddrsSyncChar() and SetTxHdlcAddrsSyncChar() are programmed with different values, the receiver will never synchronize to the transmitted frames.

  • Wrong clock mode (Gen5) — the Tx channel must be set to external clock (TXEXT_RXEXT) and the Rx channel must drive the clock internally (TXINT_RXINT). Reversing these assignments breaks clock synchronization and no data will be received.

  • No physical connection (Gen5) — Gen5 HDLC loopback requires an external RS-422 wrap cable between the Tx and Rx channels. Without it, the Tx channel has no clock source and the Rx channel receives no data.

  • SC3 module — SC3 does not support synchronous protocols. The application rejects SC3 modules during startup validation (see Board Connection and Module Selection).

Data Transmission and Reception

After configuration is complete, both the Gen2/3 and Gen5 paths follow the same high-level sequence: build test data, load the Tx FIFO, initiate transmission, read back received data, and verify byte-by-byte. The differences lie in which reception API is called and how frame boundaries are detected.

Transmission (both)

Both paths build an identical 10-byte test payload — values 0x31 through 0x3A (ASCII 1 through :) — and transmit it in the same way:

for (i = 0; i < NUM_DATA_TX; i++)
   SendData[i] = (uint8_t)(i + 0x31);  /* incremental data */

nNumWordsSend = i;
printf("Sending %d words ...", nNumWordsSend);

/* Load the transmit FIFO with data. */
naibrd_SER_TransmitBuffer(cardIndex, module, chanNum, sizeof(SendData[0]), SendData, nNumWordsSend, (uint32_t*)&nNumWordsRecv);
/* Initiate Transmit */
naibrd_SER_TransmitInitiate(cardIndex, module, chanNum);
printf(" %d words sent\n", nNumWordsRecv);
  • naibrd_SER_TransmitBuffer() loads the raw data bytes into the channel’s Tx FIFO (line 182 for Gen2/3, line 284 for Gen5).

  • naibrd_SER_TransmitInitiate() tells the hardware to begin transmission (line 184 for Gen2/3, line 286 for Gen5).

  • The hardware automatically wraps the data in a complete HDLC frame — sync characters, payload, and CRC. The application provides only the raw data bytes; it never constructs framing or computes CRC values.

Reception — Gen2/3

Gen2/3 modules use naibrd_SER_GetHDLCPacket32(), which blocks until a complete HDLC frame is received (EOF detected) or the timeout expires:

nNumWordsRecv = 0;
check_status(naibrd_SER_GetHDLCPacket32(cardIndex, module, chanNum, RecvDataStatus, MAX_DATA_RX,
   (uint32_t*)&nNumWordsRecv, MAX_TIMEOUT));
printf(" %d words read\n", nNumWordsRecv);

for (i = 0; i < nNumWordsRecv; i++)
   printf("Sent 0x%02X; Recd 0x%02X, Status= %02X\n",
      SendData[i], (RecvDataStatus[i] & 0x00FF), (RecvDataStatus[i] >> 8) & 0x00FF);
  • naibrd_SER_GetHDLCPacket32(cardIndex, module, chanNum, RecvDataStatus, MAX_DATA_RX, &nNumWordsRecv, MAX_TIMEOUT) — reads up to MAX_DATA_RX 32-bit words, blocking until the hardware detects an end-of-frame or the 10 ms timeout (MAX_TIMEOUT) expires (lines 191-192).

  • Each 32-bit word packs data in the lower byte (& 0x00FF) and status in the upper byte (>> 8 & 0x00FF).

  • Typical output: Sent 0x31; Recd 0x31, Status= 40 — status 0x40 is NAI_SER_SYNC_BOF (beginning of frame) on the first byte.

Reception — Gen5

Gen5 modules use naibrd_SER_ReceiveBuffer32(), which reads all available data from the Rx FIFO without blocking on a timeout. The application is responsible for parsing frame boundaries from the status bytes:

nNumWordsRecv = 0;
check_status(naibrd_SER_ReceiveBuffer32(cardIndex, module, rxChanNum, RecvDataStatus, MAX_DATA_RX, (uint32_t *)&nNumWordsRecv));
printf(" %d words read\n", nNumWordsRecv);

for (i = 0; i < nNumWordsRecv; i++)
{
   printf("Sent 0x%02X; Recd 0x%02X, Status= %02X",
      SendData[i], (RecvDataStatus[i] & 0x00FF), (RecvDataStatus[i] >> 8) & 0x00FF);
   if (((RecvDataStatus[i] >> 8) & 0x00FF) == NAI_SER_SYNC_BOF)
   {
      printf("\tBeginning of HDLC frame (BOF)\n");
   }
   else if (((RecvDataStatus[i] >> 8) & 0x00FF) == NAI_SER_SYNC_EOF)
   {
      printf("\tEnd of HDLC frame (EOF)\n");
   }
   else
   {
      printf("\n");
   }
}
  • naibrd_SER_ReceiveBuffer32(cardIndex, module, rxChanNum, RecvDataStatus, MAX_DATA_RX, &nNumWordsRecv) — reads up to MAX_DATA_RX 32-bit words from the Rx FIFO in a single call with no timeout (line 295).

  • The application inspects the status byte of each word to identify frame boundaries: NAI_SER_SYNC_BOF (0x40) marks the first byte of the frame and NAI_SER_SYNC_EOF (0x02) marks the last byte (lines 302-313).

  • Unlike the Gen2/3 path, which relies on GetHDLCPacket32() to wait for a complete frame, the Gen5 path must parse BOF and EOF markers itself to determine where frames begin and end.

Verification

After reception, both paths print each sent/received byte pair alongside the status byte so the user can confirm a successful loopback:

  • Data match — the lower byte of each received 32-bit word should equal the corresponding sent byte (0x31 through 0x3A).

  • Status flags — the first byte should carry NAI_SER_SYNC_BOF (0x40), the last byte should carry NAI_SER_SYNC_EOF (0x02), and all intermediate bytes should show 0x00 (normal data within frame).

  • Error flags — if the status byte contains NAI_SER_SYNC_ER0 (0x04), NAI_SER_SYNC_ER1 (0x08), or NAI_SER_SYNC_ER2 (0x10), a CRC mismatch or framing error has occurred. Check your configuration for inconsistencies.

Important
  • No data received — verify that the receiver is enabled (SetReceiverEnable), the correct channel is being read (Gen5 reads from the Rx channel, not the Tx channel), and the cable is connected (Gen5 external loopback).

  • Status shows error flags — CRC mismatch, framing error, or a configuration inconsistency between the Tx and Rx channels. Verify that protocol, baud rate, and (for Gen2/3) sync characters match on both sides.

  • Timeout on GetHDLCPacket32 (Gen2/3) — the frame never completed. Check that sync characters match, the receiver is enabled, and transmission was initiated successfully.

  • Data received but no BOF/EOF markers — the wrong reception function may be in use, or the protocol is not set to HDLC. Ensure SetProtocol(NAI_SER_PROTOCOL_HDLC) was called before transmission.

Troubleshooting Reference

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

Diagnosing HDLC Loopback Failures

When an HDLC loopback test fails, first verify the protocol is set to HDLC (not async) on all channels involved. For Gen2/3 internal loopback, confirm the Rx and Tx sync characters match (both must be the same value, e.g., 0x247A). For Gen5 external loopback, confirm the physical wrap cable is connected correctly (Tx+ to Rx+, Tx- to Rx-, Clk+ to Clk+, Clk- to Clk-) and the clock mode is correct (Tx receives clock externally, Rx drives it). Check the status bytes in received data — NAI_SER_SYNC_BOF (0x40) should appear on the first byte and NAI_SER_SYNC_EOF (0x02) on the last. Any ER0, ER1, or ER2 flags indicate a hardware-detected error such as CRC failure or framing corruption. Consult your module’s manual for detailed status bit definitions.

Error / Symptom Possible Causes Suggested Resolution

No board found or connection timeout

Board not powered, missing configuration file, network issue

Verify hardware and configuration. If file doesn’t exist, configure and save from board menu.

SC3 module used

SC3 does not support synchronous serial communications

Use a different serial module (P8, PC, PD, Px, KB for Gen2/3; SC5 or other Gen5 module).

FIFO clear timeout

Channel in use or hardware not responding

Verify module is operational and channel is not locked by another process.

Sync character mismatch (Gen2/3)

Rx and Tx sync characters set to different values

Set both SetRxHdlcAddrsSyncChar() and SetTxHdlcAddrsSyncChar() to the same value (e.g., 0x247A).

Wrong clock mode (Gen5)

Tx and Rx channels using the same clock source

Set Tx channel to TXEXT_RXEXT (receives clock) and Rx channel to TXINT_RXINT (drives clock).

No physical connection (Gen5)

Gen5 external loopback requires physical RS-422 wrap cable

Connect Tx+→Rx+, Tx-→Rx-, Clk+→Clk+, Clk-→Clk-. Consult board manual for pin assignments.

No data received

Receiver not enabled, wrong channel selected (Gen5), or cable not connected

Verify SetReceiverEnable() was called, correct Rx channel is targeted, and physical connection exists (Gen5).

Timeout on GetHDLCPacket32 (Gen2/3)

Frame never completed (no EOF received within timeout)

Verify sync characters match, receiver is enabled, and data was actually transmitted.

CRC or framing errors in status bytes

Configuration mismatch between Tx and Rx, signal integrity issue on cable (Gen5)

Verify protocol, baud rate, and clock settings match on both channels. Check cable quality (Gen5).

Data received but no BOF/EOF markers

Wrong reception function or protocol not set to HDLC

Verify SetProtocol(NAI_SER_PROTOCOL_HDLC) was called. Gen2/3 should use GetHDLCPacket32(), Gen5 uses ReceiveBuffer32().

Wrong protocol set (async instead of HDLC)

Protocol parameter is NAI_SER_PROTOCOL_ASYNC instead of NAI_SER_PROTOCOL_HDLC

Verify the SetProtocol() call uses NAI_SER_PROTOCOL_HDLC (0x0003).

Full Source

The complete source for this sample is provided below for reference. The sections above explain each part in detail.

Full Source — SER_HDLC_LOOPBACK.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_ser.h"
#include "advanced/nai_ether_adv.h"

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

/* Function prototypes */
void Run_SER_HDLC_LOOPBACK(int32_t cardIndex, int32_t module, uint32_t moduleID);

#define NUM_DATA_TX  10
#define MAX_DATA_RX  NUM_DATA_TX
#define MAX_TIMEOUT  10    /* 10ms timeout */

/**************************************************************************************************************/
/** \defgroup SERLoopbackHDLC Serial Loopback - Synchronous/HDLC
The purpose of the Serial Loopback Synchronous Sample Application is to illustrate the methods to call in the
naibrd library to configure a given serial channel for loopback HDLC transmission. In the case that the NAI
serial module is a Gen 2 or Gen 3 module, internal loopback on a single channel will be used. In the case that
the NAI serial module is a Gen 5 module, external loopback on two channels will be used (via a physical connection)
The application will write data to the selected serial channel's transmit buffer, transmit it via an internal or
physical loopback, and receive and print the data.

Using a wrap connection between 2 channels (one tx channel and one rx channel) Tx and Rx channels are user
defined, but the user must connect these channels as follows:

\verbatim
Tx Channel   |   Rx Channel
  Tx+       -->    Rx+
  Tx-       -->    Rx-
  Clk+      -->    Clk+
  Clk-      -->    Clk-
\endverbatim
*/
/**************************************************************************************************************/
#if defined (__VXWORKS__)
int32_t SER_HDLC_Sample(void)
#else
int32_t main(void)
#endif
{
   bool_t stop = FALSE;
   int32_t cardIndex;
   int32_t moduleCnt;
   int32_t module;
   uint32_t moduleID = 0;
   uint32_t fpgaRev, modProcRev;
   int8_t inputBuffer[80];
   int32_t inputResponseCnt;

   if (naiapp_RunBoardMenu(CONFIG_FILE) == (bool_t)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)
            {
               /* Check to make sure the module is NOT an SC3 */
               moduleID = naibrd_GetModuleID(cardIndex, module);
               check_status(naibrd_GetModuleRev(cardIndex, module, &modProcRev, &fpgaRev));
               if (moduleID == 0)
               {
                  printf("\nInvalid Module\n");
               }
               else if ((moduleID == NAI_MODULE_ID_SC3) && (fpgaRev < NAI_SC3_REV_1_0))
               {
                  printf("\nSC3 does not support synchronous serial communications.\n");
               }
               else
               {
                  Run_SER_HDLC_LOOPBACK(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;
}

/**************************************************************************************************************/
/** \ingroup SERLoopbackHDLC
Gen 2/3 Serial Module: Configures a serial module for synchronous internal loopback transmission. The user is queried
for which channel they would like to internal loopback on.

Gen 5 Serial module: Configures the serial module for synchronous transmission between two channels on the same
module. The user is queried for which channels they would like to transmit and receive on.
\param cardIndex (Input) Logical Card Index assigned to connection with the NAI_BOARD (0 - NAI_MAX_CARDS-1).
\param module    (Input) Module Number of the module to access (1 - [max modules for board]).
\param moduleID  (Input) The ID of the module.
*/
/**************************************************************************************************************/
void Run_SER_HDLC_LOOPBACK(int32_t cardIndex, int32_t module, uint32_t moduleID)
{
   int32_t ch, i;
   int32_t channelCount;
   int32_t nNumWordsSend, nNumWordsRecv;
   int32_t nCntlValueLo, nConfigWordLo;
   uint32_t SendData[NUM_DATA_TX], RecvDataStatus[MAX_DATA_RX];

   channelCount = naibrd_SER_GetChannelCount(moduleID);

   if (NAI_MODULE_ID_P8 == moduleID || NAI_MODULE_ID_PC == moduleID || NAI_MODULE_ID_PD == moduleID ||
      NAI_MODULE_ID_Px == moduleID || NAI_MODULE_ID_KB == moduleID)
   {
      /*************** Gen 2/3 ******************/
      int32_t chanNum;

      naiapp_query_ChannelNumber(channelCount, 1, &chanNum);

      check_status(naibrd_SER_ChannelReset(cardIndex, module, chanNum));
      check_status(naibrd_SER_ClearRxFifo(cardIndex, module, chanNum));
      check_status(naibrd_SER_ClearTxFifo(cardIndex, module, chanNum));
      nCntlValueLo = NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO;
      for (i = 0; i < 1000 && (nCntlValueLo & (NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO)); i++)
      {
         nai_ser_chanctrl chanCtrlRaw;
         check_status(naibrd_SER_GetChannelControlRaw(cardIndex, module, chanNum, &chanCtrlRaw));
         nCntlValueLo = chanCtrlRaw & 0x0000FFFF;
         nai_msDelay(1);
      }
      if (i == 1000)
      {
         printf("Unable to clear FIFOs %d\n", chanNum);
         printf("Please press Enter to exit...");
         while ((ch = getchar()) != 0x0A);
         return;
      }

      printf("\nSerial Channel # %d\n", chanNum);
      check_status(naibrd_SER_SetProtocol(cardIndex, module, chanNum, NAI_SER_PROTOCOL_HDLC));        /* HDLC mode */
      check_status(naibrd_SER_SetInterfaceLevel(cardIndex, module, chanNum, NAI_SER_INTF_LOOPBACK));  /* LoopBack */
      check_status(naibrd_SER_SetClockMode(cardIndex, module, chanNum, TXINT_RXINT));                 /* Tx and Rx internal */
      check_status(naibrd_SER_SetBaudrate(cardIndex, module, chanNum, 115200));                       /* 115,200 HDLC/sync mode */
      check_status(naibrd_SER_SetRxHdlcAddrsSyncChar(cardIndex, module, chanNum, 0x247A));            /* recv sync char - must be same as xmit for loopback */
      check_status(naibrd_SER_SetTxHdlcAddrsSyncChar(cardIndex, module, chanNum, 0x247A));            /* xmit sync char - must be same as recv for loopback */
      nConfigWordLo = (NAI_SER_CFGLO_ADDR_LEN_16 | NAI_SER_CFGLO_ADDR_REC);                           /* 16-bit HDLC addr recognition */
      check_status(naibrd_SER_SetChannelConfigRaw(cardIndex, module, chanNum, nConfigWordLo));

      nai_msDelay(20);     /* Allow 20ms for the HW to acknowledge the configuration (GEN 2/3 only)*/

      check_status(naibrd_SER_SetReceiverEnable(cardIndex, module, chanNum, 1));                      /* Enable the Receiver */

      /* 100 millisecond wait to ensure the receiver is on. */
      nai_msDelay(100);

      for (i = 0; i < NUM_DATA_TX; i++)
         SendData[i] = (uint8_t)(i + 0x31);  /* incremental data */

      nNumWordsSend = i;
      printf("Sending %d words ...", nNumWordsSend);

      /* Load the transmit FIFO with data. */
      naibrd_SER_TransmitBuffer(cardIndex, module, chanNum, sizeof(SendData[0]), SendData, nNumWordsSend, (uint32_t*)&nNumWordsRecv);
      /* Initiate Transmit */
      naibrd_SER_TransmitInitiate(cardIndex, module, chanNum);
      printf(" %d words sent\n", nNumWordsRecv);

      printf("Please press Enter to read back data...");
      while ((ch = getchar()) != 0x0A);

      nNumWordsRecv = 0;
      check_status(naibrd_SER_GetHDLCPacket32(cardIndex, module, chanNum, RecvDataStatus, MAX_DATA_RX,
         (uint32_t*)&nNumWordsRecv, MAX_TIMEOUT));
      printf(" %d words read\n", nNumWordsRecv);

      for (i = 0; i < nNumWordsRecv; i++)
         printf("Sent 0x%02X; Recd 0x%02X, Status= %02X\n",
            SendData[i], (RecvDataStatus[i] & 0x00FF), (RecvDataStatus[i] >> 8) & 0x00FF);
   }
   else
   {
      /************************************************ Gen5 ***************************************/
      /* Using a wrap connection between 2 channels (one tx channel and one rx channel)            */
      /* Tx and Rx channels are user defined, but the user must connect these channels as follows: */
      /*                                                                                           */
      /*     Tx Channel   |   Rx Channel                                                           */
      /*       Tx+       -->    Rx+                                                                */
      /*       Tx-       -->    Rx-                                                                */
      /*       Clk+      -->    Clk+                                                               */
      /*       Clk-      -->    Clk-                                                               */
      /*********************************************************************************************/

      int32_t txChanNum, rxChanNum;

      printf("\nSelect Transmitter channel.");
      naiapp_query_ChannelNumber(channelCount, 1, &txChanNum);
      printf("Select Receiver channel.");
      naiapp_query_ChannelNumber(channelCount, 2, &rxChanNum);

      /* Reset and clear Transmitters FIFOs. */
      check_status(naibrd_SER_ChannelReset(cardIndex, module, txChanNum));
      check_status(naibrd_SER_ClearRxFifo(cardIndex, module, txChanNum));
      check_status(naibrd_SER_ClearTxFifo(cardIndex, module, txChanNum));
      nCntlValueLo = NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO;
      for (i = 0; i < 1000 && (nCntlValueLo & (NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO)); i++)
      {
         nai_ser_chanctrl chanCtrlRaw;
         check_status(naibrd_SER_GetChannelControlRaw(cardIndex, module, txChanNum, &chanCtrlRaw));
         nCntlValueLo = chanCtrlRaw & 0x0000FFFF;
         nai_msDelay(1);
      }
      if (i == 1000)
      {
         printf("Unable to clear Transmitter FIFOs %d\n", txChanNum);
         printf("Please press Enter to exit...");
         while ((ch = getchar()) != 0x0A);
         return;
      }

      /* Reset and clear Receivers FIFOs. */
      check_status(naibrd_SER_ChannelReset(cardIndex, module, rxChanNum));
      check_status(naibrd_SER_ClearRxFifo(cardIndex, module, rxChanNum));
      check_status(naibrd_SER_ClearTxFifo(cardIndex, module, rxChanNum));
      nCntlValueLo = NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO;
      for (i = 0; i < 1000 && (nCntlValueLo & (NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO)); i++)
      {
         nai_ser_chanctrl chanCtrlRaw;
         check_status(naibrd_SER_GetChannelControlRaw(cardIndex, module, rxChanNum, &chanCtrlRaw));
         nCntlValueLo = chanCtrlRaw & 0x0000FFFF;
         nai_msDelay(1);
      }
      if (i == 1000)
      {
         printf("Unable to clear Receiver FIFOs %d\n", rxChanNum);
         printf("Please press Enter to exit...");
         while ((ch = getchar()) != 0x0A);
         return;
      }

      /* Configure Transmitter. */
      printf("\nTransmitter Serial Channel # %d\n", txChanNum);
      check_status(naibrd_SER_SetProtocol(cardIndex, module, txChanNum, NAI_SER_PROTOCOL_HDLC));            /* HDLC mode */
      check_status(naibrd_SER_SetInterfaceLevel(cardIndex, module, txChanNum, NAI_SER_GEN5_INTF_RS422));    /* RS422 */
      check_status(naibrd_SER_SetClockMode(cardIndex, module, txChanNum, TXEXT_RXEXT));                     /* External clock (receiving clock input). */
      check_status(naibrd_SER_SetBaudrate(cardIndex, module, txChanNum, 1000000));                          /* 1Mbps */

      /* Configure Receiver. */
      printf("Receiver Serial Channel # %d\n", rxChanNum);
      check_status(naibrd_SER_SetProtocol(cardIndex, module, rxChanNum, NAI_SER_PROTOCOL_HDLC));          /* HDLC mode */
      check_status(naibrd_SER_SetInterfaceLevel(cardIndex, module, rxChanNum, NAI_SER_GEN5_INTF_RS422));  /* RS422 */
      check_status(naibrd_SER_SetClockMode(cardIndex, module, rxChanNum, TXINT_RXINT));                   /* Internal clock (driving clock). */
      check_status(naibrd_SER_SetBaudrate(cardIndex, module, rxChanNum, 1000000));                        /* 1Mbps*/
      check_status(naibrd_SER_SetReceiverEnable(cardIndex, module, rxChanNum, 1));                        /* Enable the Receiver */

      /* 100 millisecond wait to ensure the receiver is on. */
      //nai_msDelay(100);    /* TODO: Remove me.. */

      for (i = 0; i < NUM_DATA_TX; i++)
         SendData[i] = (uint8_t)(i + 0x31);  /* incremental data */

      nNumWordsSend = i;
      printf("Sending %d words ...", nNumWordsSend);

      /* Load the transmit FIFO with data. */
      naibrd_SER_TransmitBuffer(cardIndex, module, txChanNum, sizeof(SendData[0]), SendData, nNumWordsSend, (uint32_t*)&nNumWordsRecv);
      /* Initiate Transmit. */
      naibrd_SER_TransmitInitiate(cardIndex, module, txChanNum);
      printf(" %d words sent\n", nNumWordsRecv);

      nai_msDelay(5);

      printf("Please press Enter to read back data...");
      while ((ch = getchar()) != 0x0A);

      nNumWordsRecv = 0;
      check_status(naibrd_SER_ReceiveBuffer32(cardIndex, module, rxChanNum, RecvDataStatus, MAX_DATA_RX, (uint32_t *)&nNumWordsRecv));
      printf(" %d words read\n", nNumWordsRecv);

      for (i = 0; i < nNumWordsRecv; i++)
      {
         printf("Sent 0x%02X; Recd 0x%02X, Status= %02X",
            SendData[i], (RecvDataStatus[i] & 0x00FF), (RecvDataStatus[i] >> 8) & 0x00FF);
         if (((RecvDataStatus[i] >> 8) & 0x00FF) == NAI_SER_SYNC_BOF)
         {
            printf("\tBeginning of HDLC frame (BOF)\n");
         }
         else if (((RecvDataStatus[i] >> 8) & 0x00FF) == NAI_SER_SYNC_EOF)
         {
            printf("\tEnd of HDLC frame (EOF)\n");
         }
         else
         {
            printf("\n");
         }
      }
   }

   return;
}

Help Bot

X