SER ASync GPIO
Edit this on GitLab
SER ASync GPIO Sample Application (SSK 1.x)
Overview
The SER ASync GPIO sample application demonstrates how to use NAI serial module channels as general-purpose digital I/O (GPIO) rather than as serial communication interfaces. Using the SSK 1.x API, you can repurpose a serial channel’s physical pins to drive a digital output (GPO) and read a digital input (GPI), which is useful when your system needs a small number of discrete digital signals without dedicating a separate digital I/O module.
In normal serial mode, a channel’s pins carry asynchronous or synchronous serial data (RS-232, RS-422, RS-485, etc.). In GPIO mode, those same pins become simple high/low digital lines. The sample configures a channel for GPIO mode with loopback enabled so the GPO line feeds directly back to the GPI line, letting you verify GPIO operation without external wiring.
This sample supports the SC3 serial module. Combination modules that include serial functionality (such as CMH) may also support GPIO mode — consult your module’s manual to confirm GPIO availability on your specific hardware.
Prerequisites
Before running this sample, make sure you have:
-
An NAI board with an SC3 serial module installed.
-
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_GPIO executable from your build output directory. On startup the application looks for a configuration file (default_SerASync_GPIO.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 prompts you to select a serial channel, configures it for GPIO mode, and lets you toggle the output line interactively.
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 serial GPIO. For details on board connection configuration, see the First Time Setup Guide. |
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_GPIO.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()and verify it is an SC3 module before proceeding.
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) && (moduleID == NAI_MODULE_ID_SC3))
{
Run_SER_ASync_GPIO(cardIndex, module, moduleID);
}
else
{
printf("\nThe GPIO feature is only available on the NAI SC3 module.\n\n");
}
}
}
}
}
The application explicitly checks for NAI_MODULE_ID_SC3 because GPIO mode is an SC3-specific feature. If you select a module that does not support GPIO, the application prints a message and returns to the selection prompt.
|
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 SER_ASync_GPIO_Sample() — the SSK 1.x build system selects the correct variant via a preprocessor guard:
#if defined (__VXWORKS__)
int32_t SER_ASync_GPIO_Sample(void)
#else
int32_t main(void)
#endif
The startup flow is the same in both cases:
-
Attempt to load the saved configuration file via
naiapp_RunBoardMenu(CONFIG_FILE). If the file does not yet exist, the interactive board menu is presented instead. -
Enter a loop that queries for card index and module slot.
-
Validate the module is SC3, then call
Run_SER_ASync_GPIO()to enter the GPIO configuration and toggle loop. -
On exit, close all open board connections with
naiapp_access_CloseAllOpenCards().
Application Flow
Unlike the menu-driven BasicOps samples, this application follows a linear flow: connect to board, select a channel, configure GPIO mode, then enter an interactive loop that toggles the output and reads the input. The key values your application needs to track are:
-
cardIndex— identifies which board in a multi-board system. -
module— the slot number where the serial module is installed. -
chanNum— the serial channel to configure for GPIO mode. -
control— the raw channel control register value, used to set and clear the GPO bit.
The menu system is a sample convenience — in your own code, call these API functions directly.
Channel Reset and FIFO Clear
Before configuring a serial channel for GPIO mode, you must reset it and clear its transmit and receive FIFOs. This ensures the channel starts in a known state with no stale data.
To reset a channel and clear its FIFOs, call naibrd_SER_ChannelReset(), naibrd_SER_ClearRxFifo(), and naibrd_SER_ClearTxFifo():
naibrd_SER_ChannelReset(cardIndex, module, chanNum);
naibrd_SER_ClearRxFifo(cardIndex, module, chanNum);
naibrd_SER_ClearTxFifo(cardIndex, module, chanNum);
After issuing the clear commands, you must poll the channel control register to confirm the FIFO clear operations have completed. The hardware clears the NAI_SER_CTRLLO_CLEAR_RX_FIFO and NAI_SER_CTRLLO_CLEAR_TX_FIFO bits when the operation finishes:
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);
return;
}
The CLEAR_FIFO_TIMEOUT constant is set to 1000 (1 second). Each iteration polls the control register via naibrd_SER_GetChannelControlRaw() and waits 1 ms with nai_msDelay(). If the bits have not cleared within the timeout, the FIFO clear has failed and you should not proceed with GPIO configuration.
-
cardIndex— logical card index (0-based). -
module— module slot number (1-based). -
chanNum— serial channel number (1-based).
|
Important
|
Common Errors
|
GPIO Mode Configuration
This is the core of the sample: switching a serial channel from its normal serial mode into GPIO mode. In GPIO mode, the channel’s physical pins function as a digital output (GPO) and digital input (GPI) rather than carrying serial data.
Setting the Interface Level for GPIO
To configure a channel for GPIO mode, call naibrd_SER_SetInterfaceLevel() with the NAI_SER_GEN5_INTF_GP_OUT flag. The sample also combines this with NAI_SER_INTF_LOOPBACK to internally connect GPO to GPI for testing:
check_status(naibrd_SER_SetInterfaceLevel(cardIndex, module, chanNum,
NAI_SER_INTF_LOOPBACK | NAI_SER_GEN5_INTF_GP_OUT));
-
NAI_SER_GEN5_INTF_GP_OUT— enables GPIO mode on the channel. The channel’s transmit pin becomes a general-purpose output (GPO) and the receive pin becomes a general-purpose input (GPI). -
NAI_SER_INTF_LOOPBACK— enables internal loopback, connecting the GPO line back to the GPI line. This is used for verification without external wiring. In your production application, omit this flag and connect external signals to the GPI pin instead.
In your own application, if you only need GPIO output without loopback, pass NAI_SER_GEN5_INTF_GP_OUT alone. If you need to read external signals on GPI, wire your signal source to the channel’s receive pin and omit NAI_SER_INTF_LOOPBACK.
Enabling the Receiver
After setting the interface level, enable the channel’s receiver so GPI status can be read:
check_status(naibrd_SER_SetReceiverEnable(cardIndex, module, chanNum, TRUE));
Pass TRUE to enable the receiver. Without this step, GPI reads will not reflect the actual pin state.
Configuration Settling Delay
The sample inserts a 500 ms delay after configuration to allow the hardware to stabilize:
nai_msDelay(500);
This delay ensures the interface level change and receiver enable have taken effect before you begin reading or writing GPIO state. Consult your module’s manual for the recommended settling time for your specific hardware revision.
Initializing GPO to a Known State
Before entering the toggle loop, the sample reads the current channel control register and explicitly clears the GPO bit to ensure the output starts low:
check_status(naibrd_SER_GetChannelControlRaw(cardIndex, module, chanNum, &control));
control &= ~NAI_SER_GEN5_CTRL_GPO1;
check_status(naibrd_SER_SetChannelControlRaw(cardIndex, module, chanNum, control));
The NAI_SER_GEN5_CTRL_GPO1 constant represents the GPO bit within the channel control register. Use a read-modify-write pattern: read the current register value, clear or set the GPO bit, then write the value back. This preserves any other control bits that may be set.
|
Important
|
Common Errors
|
Toggling GPO and Reading GPI
Once the channel is configured for GPIO mode, you can drive the output high or low and read the corresponding input state. The sample demonstrates this in an interactive loop.
Setting GPO High
To drive the general-purpose output high, set the NAI_SER_GEN5_CTRL_GPO1 bit in the channel control register:
control |= NAI_SER_GEN5_CTRL_GPO1;
check_status(naibrd_SER_SetChannelControlRaw(cardIndex, module, chanNum, control));
Setting GPO Low
To drive the output low, clear the NAI_SER_GEN5_CTRL_GPO1 bit:
control &= ~NAI_SER_GEN5_CTRL_GPO1;
check_status(naibrd_SER_SetChannelControlRaw(cardIndex, module, chanNum, control));
Reading GPI State
After changing GPO, read the GPI state from the channel’s real-time status register using naibrd_SER_GetChannelStatusEx():
uint32_t chanStatus = 0;
check_status(naibrd_SER_GetChannelStatusEx(cardIndex, module, chanNum,
NAI_SER_STATUS_REALTIME, &chanStatus));
printf("GPI bit: %u\n", (chanStatus & NAI_SER_GEN5_INT_GPI1) >> 15);
-
NAI_SER_STATUS_REALTIME— reads the real-time (live) status register rather than a latched status. This gives you the current pin state at the moment of the read. -
NAI_SER_GEN5_INT_GPI1— the bitmask for the GPI bit within the status register. The shift>> 15isolates it as a 0 or 1 value.
With loopback enabled, setting GPO high causes GPI to read 1, and setting GPO low causes GPI to read 0. In your production application without loopback, GPI reflects whatever external signal is connected to the channel’s receive pin.
The Toggle Loop
The sample runs a simple interactive loop that alternates between pulling GPO high and low:
do
{
printf("Press ENTER to pull GPO high, or %c to exit program...\n", NAI_QUIT_CHAR);
bQuit = naiapp_query_ForQuitResponse(sizeof(inputBuffer), NAI_QUIT_CHAR, inputBuffer, &inputResponseCnt);
if (TRUE != bQuit)
{
control |= NAI_SER_GEN5_CTRL_GPO1;
check_status(naibrd_SER_SetChannelControlRaw(cardIndex, module, chanNum, control));
check_status(naibrd_SER_GetChannelStatusEx(cardIndex, module, chanNum, NAI_SER_STATUS_REALTIME, &chanStatus));
printf("GPI bit: %u\n", (chanStatus & NAI_SER_GEN5_INT_GPI1) >> 15);
printf("Press ENTER to pull GPO low, or %c to exit program...\n", NAI_QUIT_CHAR);
bQuit = naiapp_query_ForQuitResponse(sizeof(inputBuffer), NAI_QUIT_CHAR, inputBuffer, &inputResponseCnt);
}
if (TRUE != bQuit)
{
control &= ~NAI_SER_GEN5_CTRL_GPO1;
check_status(naibrd_SER_SetChannelControlRaw(cardIndex, module, chanNum, control));
check_status(naibrd_SER_GetChannelStatusEx(cardIndex, module, chanNum, NAI_SER_STATUS_REALTIME, &chanStatus));
printf("GPI bit: %u\n", (chanStatus & NAI_SER_GEN5_INT_GPI1) >> 15);
}
} while (TRUE != bQuit);
In your own application, replace the interactive prompts with your application logic. The key API pattern is the same: use naibrd_SER_SetChannelControlRaw() to drive GPO and naibrd_SER_GetChannelStatusEx() to read GPI.
|
Important
|
Common Errors
|
Troubleshooting Reference
The following table summarizes common errors and symptoms you may encounter when working with serial GPIO mode. Consult your module’s manual for hardware-specific diagnostics and specifications.
| Error / Symptom | Possible Causes | Suggested Resolution |
|---|---|---|
FIFO clear timeout |
Hardware not responding; module fault. |
Power-cycle the board. If persistent, check module seating and consult the module manual. |
"The GPIO feature is only available on the NAI SC3 module" |
Selected module is not SC3; GPIO mode is SC3-specific. |
Select the correct module slot containing an SC3. Use the board menu to identify installed modules. |
|
API call targeting a feature the current module does not support. |
Verify the module ID is |
|
Channel number out of range for the module. |
Call |
GPI always reads 0 |
Receiver not enabled; settling delay too short; GPIO mode not set. |
Call |
GPI does not follow GPO in loopback |
Loopback flag not included in interface level; receiver not enabled. |
Verify the interface level was set with both |
GPO does not drive external pin |
Loopback mode routes GPO internally, not to the external connector. |
Remove |
No board found / connection timeout |
Board not powered; incorrect interface or address; firewall blocking connection. |
Verify power and physical connection. Check the configuration file for correct interface settings. |
Full Source
Full Source — SER_ASync_GPIO.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_GPIO.txt";
/* Function prototypes */
void Run_SER_ASync_GPIO(int32_t cardIndex, int32_t module, uint32_t moduleID);
#define CLEAR_FIFO_TIMEOUT 1000 /* 1 second */
/**************************************************************************************************************/
/** \defgroup SERGPIO Serial General Purpose I/O
The purpose of the Serial General Purpose I/O sample application is to illustrate the methods to call in the
naibrd library to configure a given serial channel for GPIO mode and toggle the output. In loopback, the general
purpose input and general purpose output lines are connected together.
*/
/**************************************************************************************************************/
#if defined (__VXWORKS__)
int32_t SER_ASync_GPIO_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);
if ((moduleID != 0) && (moduleID == NAI_MODULE_ID_SC3))
{
Run_SER_ASync_GPIO(cardIndex, module, moduleID);
}
else
{
printf("\nThe GPIO feature is only available on the NAI SC3 module.\n\n");
}
}
}
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 SERGPIO
Configures a serial module for GPIO mode. The user can than the toggle the general purpose output
and monitor the general purpose input.
\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_GPIO(int32_t cardIndex, int32_t module, uint32_t moduleID)
{
int32_t ch, i;
int32_t channelCount;
int32_t chanNum;
int32_t nCntlValueLo;
bool_t bQuit = FALSE;
uint32_t control = 0;
int8_t inputBuffer[80];
int32_t inputResponseCnt;
channelCount = naibrd_SER_GetChannelCount(moduleID);
naiapp_query_ChannelNumber(channelCount, 1, &chanNum);
naibrd_SER_ChannelReset(cardIndex, module, 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 = getchar()) != 0x0A);
return;
}
printf("\nSerial Channel # %d\n", chanNum);
/* Configure GPIO + Loopback on the channel selected */
check_status(naibrd_SER_SetInterfaceLevel(cardIndex, module, chanNum, NAI_SER_INTF_LOOPBACK | NAI_SER_GEN5_INTF_GP_OUT)); /* LoopBack with GPIO */
/* Enable receiver */
check_status(naibrd_SER_SetReceiverEnable(cardIndex, module, chanNum, TRUE));
/* Add a delay here for the configuration to be ready */
nai_msDelay(500);
/* Ensure GPO is low */
check_status(naibrd_SER_GetChannelControlRaw(cardIndex, module, chanNum, &control));
control &= ~NAI_SER_GEN5_CTRL_GPO1;
check_status(naibrd_SER_SetChannelControlRaw(cardIndex, module, chanNum, control));
do
{
printf("Press ENTER to pull GPO high, or %c to exit program...\n", NAI_QUIT_CHAR);
bQuit = naiapp_query_ForQuitResponse(sizeof(inputBuffer), NAI_QUIT_CHAR, inputBuffer, &inputResponseCnt);
if (TRUE != bQuit)
{
uint32_t chanStatus = 0;
/* Set the GPO bit to 1 (high state) */
control |= NAI_SER_GEN5_CTRL_GPO1;
check_status(naibrd_SER_SetChannelControlRaw(cardIndex, module, chanNum, control));
/* Since LOOPBACK mode ties GPO to GPI, GPI will be in a high state */
check_status(naibrd_SER_GetChannelStatusEx(cardIndex, module, chanNum, NAI_SER_STATUS_REALTIME, &chanStatus));
printf("GPI bit: %u\n", (chanStatus & NAI_SER_GEN5_INT_GPI1) >> 15);
printf("Press ENTER to pull GPO low, or %c to exit program...\n", NAI_QUIT_CHAR);
bQuit = naiapp_query_ForQuitResponse(sizeof(inputBuffer), NAI_QUIT_CHAR, inputBuffer, &inputResponseCnt);
}
if (TRUE != bQuit)
{
uint32_t chanStatus = 0;
/* Set the GPO bit to 0 (low state) */
control &= ~NAI_SER_GEN5_CTRL_GPO1;
check_status(naibrd_SER_SetChannelControlRaw(cardIndex, module, chanNum, control));
/* Since LOOPBACK mode ties GPO to GPI, GPI will be in a low state */
check_status(naibrd_SER_GetChannelStatusEx(cardIndex, module, chanNum, NAI_SER_STATUS_REALTIME, &chanStatus));
printf("GPI bit: %u\n", (chanStatus & NAI_SER_GEN5_INT_GPI1) >> 15);
}
} while (TRUE != bQuit);
return;
}