SER ASync LOOPBACK
Edit this on GitLab
SER ASync Loopback Sample Application (SSK 1.x)
Overview
The SER ASync Loopback sample application demonstrates how to configure a serial channel for asynchronous communication and run an internal loopback test to verify the transmit/receive path using the NAI Software Support Kit (SSK 1.x). The application writes data to the selected channel’s transmit buffer, loops it back internally, and reads it back for verification.
This sample supports the following SER module types: P8, PC, PD, Px, KB, and newer SER variants. It also works with combination modules that include serial functionality, such as CMH. It serves as a practical API reference for implementing async serial communication using SSK 1.x.
Prerequisites
Before running this sample, make sure you have:
-
An NAI board with a SER module installed (P8, PC, PD, Px, KB, or newer SER variant).
-
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.
How to Run
Launch the SER_ASync_LOOPBACK executable from your build output directory. On startup the application looks for a configuration file (default_SerAsync_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 number and executes a single 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:
-
Call
naiapp_RunBoardMenu()to load a saved configuration file (if one exists) or present the interactive board menu. The configuration file (default_SerAsync_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. -
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 adapt to the specific SER variant installed. -
Query the user for a channel number with
naiapp_query_ChannelNumber(). The channel count is obtained from the module ID vianaibrd_SER_GetChannelCount(), and the selected channel is passed toRun_SER_ASYNC()for the loopback test.
#if defined (__VXWORKS__)
int32_t SER_ASYNC_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;
int8_t inputBuffer[80];
int32_t inputResponseCnt;
if (naiapp_RunBoardMenu(CONFIG_FILE) == TRUE)
{
while (stop != TRUE)
{
/* Query 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);
Run_SER_ASYNC(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 entry point is main() on most platforms, or SER_ASYNC_Sample() on VxWorks.
The startup flow proceeds as follows:
-
Load the saved configuration (or present the board menu) via
naiapp_RunBoardMenu(). -
Query the user for card index, module slot, and retrieve the module ID.
-
Call
Run_SER_ASYNC(cardIndex, module, moduleID)to query for a channel and execute the loopback test. -
Prompt the user to quit or restart.
|
Note
|
Unlike menu-driven samples (such as AD BasicOps), this sample executes a single loopback test and returns. There is no interactive command loop. |
To run the loopback test, call Run_SER_ASYNC(cardIndex, module, moduleID) with three parameters:
-
cardIndex— the logical card index for the board connection. -
module— the one-based module slot number. -
moduleID— the module ID returned bynaibrd_GetModuleID(), used to determine hardware-specific behavior (for example, inserting a configuration delay on Gen 2/3 modules).
FIFO Setup
Before transmitting data, clear both the receive and transmit FIFOs to ensure no stale data remains from a previous operation. Leftover bytes in either FIFO can corrupt a loopback test or any other transfer, so this step should be the first thing you do after selecting a channel.
Clearing the FIFOs
Issue one call for each FIFO direction:
naibrd_SER_ClearRxFifo(cardIndex, module, chanNum);
naibrd_SER_ClearTxFifo(cardIndex, module, chanNum);
These API calls request the hardware to flush the respective FIFOs. The requests are asynchronous — the hardware sets control-register bits while the clear is in progress and drops them once the operation completes.
Timeout Polling Pattern
After requesting the clear, poll the channel control register until the FIFO-clear bits are deasserted. The sample uses a 1 ms polling interval with a 1-second (1 000-iteration) timeout:
nCntlValueLo = NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO;
for (i = 0; i < CLEAR_FIFO_TIMEOUT && (nCntlValueLo & (NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO)); i++)
{
nai_ser_chanctrl chanCtrlRaw;
naibrd_SER_GetChannelControlRaw(cardIndex, module, chanNum, &chanCtrlRaw);
nCntlValueLo = chanCtrlRaw & 0x0000FFFF;
nai_msDelay(1);
}
if (i == CLEAR_FIFO_TIMEOUT)
{
printf("Unable to clear FIFOs %d\n", chanNum);
printf("Please press Enter to exit...");
while ((ch = (int8_t)getchar()) != 0x0A);
return;
}
The loop reads the raw channel control word with naibrd_SER_GetChannelControlRaw(), masks it to the lower 16 bits, and checks whether NAI_SER_CTRLLO_CLEAR_RX_FIFO or NAI_SER_CTRLLO_CLEAR_TX_FIFO is still asserted. Each iteration sleeps 1 ms (nai_msDelay(1)), and CLEAR_FIFO_TIMEOUT is defined as 1 000, giving a total wait of up to 1 second. If both bits clear before the timeout expires, the loop exits early and the application continues to channel configuration.
In your own application, implement a similar check to confirm FIFOs are cleared before proceeding. Skipping this verification can lead to data being transmitted into a FIFO that is still mid-flush, producing undefined results.
|
Important
|
Common Errors
|
Channel Configuration
The sample configures the serial channel with a standard set of asynchronous parameters before enabling transmission and reception. In your own application, call the same APIs with values that match your communication requirements — the defaults shown here are a common starting point, but every parameter can be adjusted independently.
Configuration APIs
The following block sets the protocol, interface level, parity, data bits, stop bits, and baud rate for the selected channel:
check_status(naibrd_SER_SetProtocol(cardIndex, module, chanNum, NAI_SER_PROTOCOL_ASYNC)); /* Async mode */
check_status(naibrd_SER_SetInterfaceLevel(cardIndex, module, chanNum, NAI_SER_INTF_LOOPBACK)); /* Loopback */
check_status(naibrd_SER_SetParity(cardIndex, module, chanNum, NAI_SER_PARITY_NONE)); /* No Parity */
check_status(naibrd_SER_SetDataBits(cardIndex, module, chanNum, 8)); /* 8 Data Bits */
check_status(naibrd_SER_SetStopBits(cardIndex, module, chanNum, 1)); /* 1 Stop Bit */
check_status(naibrd_SER_SetBaudrate(cardIndex, module, chanNum, 9600)); /* 9600 baud */
Each call is explained below with the sample’s default value and the available alternatives:
-
naibrd_SER_SetProtocol()— sets the channel protocol to asynchronous mode (NAI_SER_PROTOCOL_ASYNC). Other options include HDLC and additional protocol modes defined by your module. Select the protocol that matches your data-link requirements. -
naibrd_SER_SetInterfaceLevel()— sets the physical interface level. The sample usesNAI_SER_INTF_LOOPBACKfor internal testing. For external communication, choose RS-232, RS-422, or RS-485 as appropriate for your cabling and hardware. Consult the module manual for the interface levels your module supports. -
naibrd_SER_SetParity()— sets the parity mode. The sample usesNAI_SER_PARITY_NONE. Other options are odd and even parity. Both endpoints in a link must agree on the same parity setting, or every received frame will flag a parity error. -
naibrd_SER_SetDataBits()— sets the number of data bits per character. The sample uses 8, which is the most common setting. 7-bit mode is also available for legacy protocols that use 7-bit ASCII. -
naibrd_SER_SetStopBits()— sets the number of stop bits. The sample uses 1. Use 2 stop bits when the remote device requires extra inter-character spacing. -
naibrd_SER_SetBaudrate()— sets the baud rate. The sample uses 9600. Consult the module manual for the full list of supported baud rates on your SER module variant.
Enable Controls
After configuring the channel parameters, explicitly enable both transmit and receive:
check_status(naibrd_SER_AsyncTransmitControl(cardIndex, module, chanNum, 1)); /* Enable Transmit */
check_status(naibrd_SER_SetReceiverEnable(cardIndex, module, chanNum, 1)); /* Enable Receiver */
Both controls default to disabled. You must call each one to begin sending or receiving data. Omitting either call is a frequent source of "no data" issues.
GEN 2/3 Module Timing
Older SER modules (P8, PC, PD, Px, KB) require a short delay between enabling transmit and enabling the receiver so the hardware has time to acknowledge the configuration change:
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)
{
nai_msDelay(20); /* Allow 20ms for the HW to acknowledge the configuration (GEN 2/3 only)*/
}
This 20 ms delay is only necessary for GEN 2 and GEN 3 modules. Newer SER variants do not require it. Consult your module manual to determine whether your hardware falls into this category.
Receiver Readiness Delay
After enabling the receiver, the sample inserts a 50 ms delay before transmitting data:
nai_msDelay(50);
This pause gives the receiver time to initialize before the first bytes arrive. If you remove or shorten this delay, the earliest transmitted bytes may be lost because the receive path is not yet ready.
|
Important
|
Common Errors
|
Data Transmission and Reception
With the channel configured, FIFOs cleared, and both transmit and receive enabled, the application builds a test payload, sends it, and reads back the loopback result. The sequence below walks through each step so you can adapt it to your own data.
Building Test Data
The sample populates an array of 8 words with incremental values (0x00 through 0x07):
for (i = 0; i < NUM_DATA_TX; i++)
SendData[i] = (uint8_t)i;
NUM_DATA_TX is defined as 8, so SendData ends up holding {0x00, 0x01, 0x02, … 0x07}. In your own application, populate the buffer with whatever payload you need to send — the API does not impose any restrictions on the data content.
Transmitting
To transmit data over a configured async serial channel, call naibrd_SER_TransmitBuffer():
naibrd_SER_TransmitBuffer(cardIndex, module, chanNum, sizeof(SendData[0]), SendData, nNumWordsSend, (uint32_t*)&nNumWordsSend);
The parameters are:
-
cardIndex,module,chanNum— identify the target channel. -
sizeof(SendData[0])— the word size in bytes. BecauseSendDatais declared asuint32_t[], this evaluates to 4. -
SendData— pointer to the data buffer. -
nNumWordsSend(input) — the number of words to transmit. -
(uint32_t*)&nNumWordsSend(output) — on return, updated with the number of words actually sent. Compare the output value against the input to confirm all data was accepted.
Checking the Receive Count
Before reading back data, query the number of words available in the receive FIFO:
naibrd_SER_GetRxBufferCnt(cardIndex, module, chanNum, (uint32_t*)&nNumWordsRecv);
This call writes the current receive-buffer word count into nNumWordsRecv. Use this value to know how many words are ready before issuing a read. In a loopback test, the count should equal the number of words you transmitted.
Receiving
Read the received data with naibrd_SER_ReceiveBuffer32():
naibrd_SER_ReceiveBuffer32(cardIndex, module, chanNum, RecvDataStatus, nNumWordsSend, (uint32_t*)&nNumWordsRecv);
Each element of RecvDataStatus is a 32-bit word that packs both the data and a status indicator:
-
Lower byte (
RecvDataStatus[i] & 0x00FF) — the received data byte. -
Upper byte (
(RecvDataStatus[i] >> 8) & 0x00FF) — the per-word status flags (parity error, framing error, etc.).
The function accepts nNumWordsSend as the maximum number of words to read and returns the actual count in nNumWordsRecv.
Verification
The sample prints each sent/received pair so you can confirm the loopback path is working:
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);
In a correctly configured loopback, the sent and received values should match exactly for every word, and the status byte should be 0x00 (no errors). A non-zero status byte indicates a per-word error such as a parity or framing fault.
|
Important
|
Common Errors
|
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.
| 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 |
Module not detected at selected slot |
No module installed at the specified slot, incorrect module number entered |
Verify hardware configuration and module slot assignment |
FIFO clear timeout |
Channel in an unexpected state, hardware not responding |
Verify the module is operational and the channel is not locked by another process |
|
Protocol or interface level not available for this module type |
Check your module type. Consult your module’s manual for supported protocols and interfaces. |
Configuration not taking effect |
Transmitter or receiver not enabled after setting parameters |
Call |
Missing GEN 2/3 timing delay |
Older modules (P8, PC, PD, Px, KB) need 20ms after configuration changes |
Check module ID and add appropriate delay. Consult module manual for timing requirements. |
Received word count does not match sent count |
Receiver not enabled, baud rate mismatch, FIFOs not cleared before test |
Verify receiver is enabled, confirm baud rate, ensure FIFOs were cleared successfully |
Status byte indicates error |
Parity or framing error detected during reception |
Verify serial settings (data bits, stop bits, parity, baud rate) match on both endpoints |
No data received |
Receiver not enabled, wrong channel selected, interface level mismatch |
Verify configuration, ensure both transmit and receive are enabled, check channel number |
Invalid baud rate |
Module does not support the requested baud rate |
Consult your module’s manual for supported baud rates |
Full Source
The complete source for this sample is provided below for reference. The sections above explain each part in detail.
Full Source — SER_ASync_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_SerAsync_LOOPBACK.txt";
/* Function prototypes */
void Run_SER_ASYNC(int32_t cardIndex, int32_t module, uint32_t moduleID);
#define NUM_DATA_TX 8
#define MAX_DATA_RX 20
#define CLEAR_FIFO_TIMEOUT 1000 /* 1 second */
/**************************************************************************************************************/
/** \defgroup SERLoopbackAsync Serial Loopback - Asynchronous
The purpose of the Serial Loopback Asynchronous Sample Application is to illustrate the methods to call in the
naibrd library to configure a given serial channel for loopback transmission. The application will write data to
the selected serial channel's transmit buffer, transmit it via an internal loopback, and receive and print the
data.
*/
/**************************************************************************************************************/
#if defined (__VXWORKS__)
int32_t SER_ASYNC_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;
int8_t inputBuffer[80];
int32_t inputResponseCnt;
if (naiapp_RunBoardMenu(CONFIG_FILE) == TRUE)
{
while (stop != TRUE)
{
/* Query 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);
Run_SER_ASYNC(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 SERLoopbackAsync
Configures a serial module for asynchronous internal loopback transmission. The user is queried for the
serial channel to perform this transmission 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_ASYNC(int32_t cardIndex, int32_t module, uint32_t moduleID)
{
int32_t i;
int32_t channelCount;
int32_t chanNum;
int32_t nNumWordsSend, nNumWordsRecv;
int32_t nCntlValueLo;
uint8_t ch;
uint32_t SendData[NUM_DATA_TX], RecvDataStatus[MAX_DATA_RX];
channelCount = naibrd_SER_GetChannelCount(moduleID);
naiapp_query_ChannelNumber(channelCount, 1, &chanNum);
naibrd_SER_ClearRxFifo(cardIndex, module, chanNum);
naibrd_SER_ClearTxFifo(cardIndex, module, chanNum);
nCntlValueLo = NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO;
for (i = 0; i < CLEAR_FIFO_TIMEOUT && (nCntlValueLo & (NAI_SER_CTRLLO_CLEAR_RX_FIFO | NAI_SER_CTRLLO_CLEAR_TX_FIFO)); i++)
{
nai_ser_chanctrl chanCtrlRaw;
naibrd_SER_GetChannelControlRaw(cardIndex, module, chanNum, &chanCtrlRaw);
nCntlValueLo = chanCtrlRaw & 0x0000FFFF;
nai_msDelay(1);
}
if (i == CLEAR_FIFO_TIMEOUT)
{
printf("Unable to clear FIFOs %d\n", chanNum);
printf("Please press Enter to exit...");
while ((ch = (int8_t)getchar()) != 0x0A);
return;
}
printf("\nSerial Channel # %d\n", chanNum);
check_status(naibrd_SER_SetProtocol(cardIndex, module, chanNum, NAI_SER_PROTOCOL_ASYNC)); /* Async mode */
check_status(naibrd_SER_SetInterfaceLevel(cardIndex, module, chanNum, NAI_SER_INTF_LOOPBACK)); /* Loopback */
check_status(naibrd_SER_SetParity(cardIndex, module, chanNum, NAI_SER_PARITY_NONE)); /* No Parity */
check_status(naibrd_SER_SetDataBits(cardIndex, module, chanNum, 8)); /* 8 Data Bits */
check_status(naibrd_SER_SetStopBits(cardIndex, module, chanNum, 1)); /* 1 Stop Bit */
check_status(naibrd_SER_SetBaudrate(cardIndex, module, chanNum, 9600)); /* 9600 baud */
check_status(naibrd_SER_AsyncTransmitControl(cardIndex, module, chanNum, 1)); /* Enable Transmit */
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)
{
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 Receiver */
for (i = 0; i < NUM_DATA_TX; i++)
SendData[i] = (uint8_t)i; /* incremental data */
/* Add a delay here for receiver to be ready */
nai_msDelay(50);
/* Send data */
nNumWordsSend = i;
printf("\nSending %d words ...", nNumWordsSend);
naibrd_SER_TransmitBuffer(cardIndex, module, chanNum, sizeof(SendData[0]), SendData, nNumWordsSend, (uint32_t*)&nNumWordsSend);
printf(" %d words sent\n", nNumWordsSend);
printf("Please press Enter to read back data...");
while ((ch = (int8_t)getchar()) != 0x0A);
/* Read back data */
naibrd_SER_GetRxBufferCnt(cardIndex, module, chanNum, (uint32_t*)&nNumWordsRecv);
printf("\nReading back %d words ...", nNumWordsRecv);
naibrd_SER_ReceiveBuffer32(cardIndex, module, chanNum, RecvDataStatus, nNumWordsSend, (uint32_t*)&nNumWordsRecv);
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);
}