↓ Skip to main content

UART Programming in VxWorks 7: A Step-by-Step Guide

UART Programming in VxWorks 7: A Step-by-Step Guide

UART (Universal Asynchronous Receiver/Transmitter) remains one of the most widely used communication interfaces in embedded systems. It is simple, inexpensive, widely supported by microcontrollers and SoCs, and well suited to console access, device configuration, diagnostics, telemetry, and low-bandwidth control communication.

VxWorks 7 provides the real-time operating-system services needed to integrate UART interfaces into embedded applications. Depending on the BSP and UART driver, developers can typically access a serial device through standard file-descriptor operations, configure communication parameters using terminal I/O interfaces, and use interrupts for responsive data handling.

This guide walks through the main steps involved in UART programming on VxWorks 7, from environment setup and device configuration to reading, writing, interrupt handling, debugging, and testing.

Because UART device names, driver APIs, interrupt vectors, and hardware registers vary between BSPs, the examples below should be treated as a practical template rather than a universal hardware-specific implementation.

🔧 Understanding UART in VxWorks 7
#

UART provides asynchronous serial communication using a small set of signals, commonly including:

  • TX for transmitted data
  • RX for received data
  • GND for the signal reference
  • Optional flow-control signals such as RTS and CTS

A UART link is normally described by parameters such as:

Parameter Example
Baud rate 115200
Data bits 8
Parity None
Stop bits 1
Flow control None

A common configuration is therefore 115200 8-N-1.

From the software perspective, VxWorks can expose serial devices through the I/O subsystem so that applications can use familiar operations such as:

open()
read()
write()
ioctl()
close()

This abstraction allows application code to work with a UART as an I/O resource without directly manipulating hardware registers.

The exact device path depends on the BSP and serial driver. Older VxWorks configurations commonly use paths such as /tyCo/0, but a different target may expose another device name.

📋 Prerequisites
#

Before implementing UART communication, make sure the development environment and target platform are ready.

VxWorks 7 Development Environment
#

You need a VxWorks 7 development environment capable of building and downloading applications to the target.

Depending on the project setup, this may include Wind River development tools, a VxWorks Image Project (VIP), the target BSP, and the required serial-driver components.

Target Hardware
#

The target board must provide a UART peripheral that is supported by the BSP or by a custom driver.

A simulator or virtual target may also be sufficient for parts of the software-development process, although hardware testing is ultimately important for validating baud-rate accuracy, electrical signaling, interrupts, and physical cabling.

Programming Knowledge
#

A basic understanding of C programming is recommended, along with familiarity with VxWorks concepts such as:

  • Tasks
  • Semaphores
  • Interrupt service routines
  • Device I/O
  • File descriptors
  • Real-time scheduling

🏗️ Setting Up the VxWorks Environment
#

The first step is to make sure the target image includes the services required by the UART device.

Configure the BSP
#

Start with a BSP that supports the target processor and board.

The BSP is responsible for hardware-specific initialization, including the UART controller, interrupt configuration, clocks, pin multiplexing, and serial-device registration.

If the board is custom, the BSP may need additional development before the UART becomes available to applications.

Include the UART Driver
#

The VxWorks image must contain the appropriate UART or serial driver components.

Depending on the hardware and BSP, the driver may already be included or may need to be selected in the VIP configuration.

A successful boot does not necessarily mean the UART device is ready for application use. The driver must also initialize the peripheral and register its device interface correctly.

Verify the Device
#

Before writing application code, verify that the expected UART device exists.

On a target using a /tyCo/x-style device name, for example:

int fd = open("/tyCo/0", O_RDWR | O_NOCTTY | O_NDELAY);

if (fd < 0)
{
    perror("Error opening UART");
    return -1;
}

The exact flags and device path can vary by VxWorks version, BSP, and serial-driver implementation.

For development and debugging, it is useful to establish that the device can be opened before moving on to more complicated interrupt-driven logic.

⚙️ Configuring UART Parameters
#

UART communication only works reliably when both endpoints agree on the communication format.

The most important parameters are:

  • Baud rate
  • Data bits
  • Parity
  • Stop bits
  • Flow control
  • Input/output processing modes

Where the VxWorks serial driver exposes POSIX-style terminal I/O, the termios interface can be used to configure these settings.

A simplified example is:

#include <termios.h>

int configureUART(int fd,
                  speed_t baud,
                  int parity,
                  int dataBits,
                  int stopBits)
{
    struct termios options;

    if (tcgetattr(fd, &options) != 0)
    {
        perror("tcgetattr");
        return -1;
    }

    /*
     * Configure input and output baud rates.
     * The exact supported speed constants depend on
     * the BSP and driver.
     */
    if (cfsetispeed(&options, baud) != 0 ||
        cfsetospeed(&options, baud) != 0)
    {
        perror("cfset*speed");
        return -1;
    }

    /*
     * Disable parity and stop-bit settings before
     * applying the requested configuration.
     */
    options.c_cflag &= ~(PARENB | PARODD | CSTOPB | CSIZE);

    switch (dataBits)
    {
        case 5:
            options.c_cflag |= CS5;
            break;

        case 6:
            options.c_cflag |= CS6;
            break;

        case 7:
            options.c_cflag |= CS7;
            break;

        case 8:
            options.c_cflag |= CS8;
            break;

        default:
            return -1;
    }

    if (parity == 'O')
    {
        options.c_cflag |= PARENB | PARODD;
    }
    else if (parity == 'E')
    {
        options.c_cflag |= PARENB;
    }

    if (stopBits == 2)
    {
        options.c_cflag |= CSTOPB;
    }

    /*
     * Enable the receiver and configure the port
     * as a local connection.
     */
    options.c_cflag |= CREAD | CLOCAL;

    /*
     * Typically use raw input/output behavior for
     * binary or protocol-oriented UART traffic.
     */
    options.c_iflag = 0;
    options.c_oflag = 0;
    options.c_lflag = 0;

    if (tcsetattr(fd, TCSANOW, &options) != 0)
    {
        perror("tcsetattr");
        return -1;
    }

    return 0;
}

For a conventional 8-N-1 configuration, the caller would select:

Baud rate: 115200
Data bits: 8
Parity:    None
Stop bits: 1

The actual baud-rate constant accepted by the driver should be verified against the BSP and VxWorks serial implementation. Do not assume that every hardware platform exposes identical termios speed definitions.

📤 Writing Data to UART
#

Once the UART has been opened and configured, data can be transmitted with write().

For example:

const char *message = "Hello, UART!\r\n";

ssize_t written = write(fd, message, strlen(message));

if (written < 0)
{
    perror("UART write failed");
}
else
{
    printf("Sent %ld bytes\n", (long)written);
}

A useful practice is to check the return value rather than assuming that every requested byte was transmitted successfully.

For small messages, a single write() may be sufficient. For larger or continuously generated data streams, applications should be designed to handle partial writes and driver buffering behavior appropriately.

📥 Reading Data from UART
#

UART data can be read using read().

A simple example is:

char buffer[256];

ssize_t received = read(fd, buffer, sizeof(buffer) - 1);

if (received > 0)
{
    buffer[received] = '\0';

    printf("Received: %s\n", buffer);
}
else if (received < 0)
{
    perror("UART read failed");
}

This example treats the received data as text, but UART is frequently used for binary protocols.

For binary data, the buffer should be handled as raw bytes rather than being null-terminated or printed as a C string.

For example:

unsigned char buffer[256];

ssize_t received = read(fd, buffer, sizeof(buffer));

if (received > 0)
{
    processPacket(buffer, (size_t)received);
}

The application should also define how it handles incomplete frames, timeouts, framing boundaries, and malformed data.

🔁 Choosing Blocking or Nonblocking I/O
#

UART applications often need to decide whether reads should block until data becomes available.

With blocking I/O, a task can sleep while waiting for input. This can be simple and efficient when the task’s only purpose is handling UART traffic.

With nonblocking or event-driven designs, the application can continue performing other work while monitoring the UART.

The correct approach depends on the system architecture.

For a dedicated communication task, blocking reads can be perfectly reasonable:

UART data arrives
       ↓
Read wakes the task
       ↓
Parse message
       ↓
Process command
       ↓
Wait for next message

For more complex real-time applications, interrupt-driven buffering can provide tighter control over latency and CPU usage.

⚡ Handling UART Interrupts
#

Interrupts become particularly useful when UART traffic must be handled promptly or when the application should avoid continuously polling the UART hardware.

Typical UART interrupt sources include:

  • Receive data available
  • Transmit buffer empty
  • Transmission complete
  • Framing errors
  • Parity errors
  • Overrun errors
  • Other hardware-specific faults

A good interrupt-driven implementation separates time-critical hardware handling from more expensive application processing.

Keep the ISR Short
#

An interrupt service routine should generally do only the work necessary to acknowledge the interrupt, capture critical state, and move data into a safe software buffer.

Avoid performing expensive operations directly inside the ISR.

For example:

UART interrupt
      ↓
Read hardware status
      ↓
Read received byte(s)
      ↓
Store in RX buffer
      ↓
Signal communication task
      ↓
Return from ISR

The communication task can then perform parsing, protocol validation, logging, or other relatively expensive operations outside interrupt context.

Connecting an ISR
#

VxWorks provides interrupt-connection mechanisms, but the exact API and vector mapping depend on the target architecture and BSP.

A conceptual example is:

void uartInterruptHandler(void *arg)
{
    /*
     * Acknowledge the UART interrupt.
     * Read status and process minimal
     * interrupt-time work here.
     */

    handleUartInterrupt(arg);
}

int attachUartInterrupt(int vector, void *parameter)
{
    STATUS status;

    status = intConnect(
        INUM_TO_IVEC(vector),
        (VOIDFUNCPTR)uartInterruptHandler,
        (int)parameter
    );

    if (status != OK)
    {
        return -1;
    }

    return 0;
}

The exact intConnect() signature, interrupt-vector conversion, argument type, and interrupt-enable operation depend on the VxWorks version, CPU architecture, and BSP.

For that reason, application code should follow the interrupt model defined by the specific target BSP rather than copying a generic vector number or register sequence.

🧺 UART Data Buffering
#

Interrupt-driven UART communication normally requires software buffers.

Without buffering, the ISR would have to process every received byte immediately, increasing the amount of work performed at interrupt level.

A ring buffer is a common solution:

             +----------------------+
UART RX ---> | Circular RX Buffer   |
             +----------------------+
                ↑              ↑
             Producer        Consumer
               ISR             Task

The ISR writes incoming bytes into the buffer, while a task removes and processes them.

A simplified conceptual structure is:

#define RX_BUFFER_SIZE 1024

typedef struct
{
    unsigned char data[RX_BUFFER_SIZE];
    volatile size_t head;
    volatile size_t tail;
} UartRingBuffer;

Careful synchronization is required when the ISR and task access the same buffer.

The implementation should define what happens when the buffer becomes full.

Possible policies include:

  • Drop the newest byte
  • Drop the oldest byte
  • Flag an overflow condition
  • Pause reception using hardware flow control
  • Apply protocol-level backpressure

The appropriate choice depends on the communication protocol.

🧠 Real-Time Considerations
#

UART communication looks simple, but the interaction between serial hardware and a real-time operating system introduces several timing considerations.

Interrupt Priority
#

UART interrupts should have an appropriate priority relative to other system interrupts.

If the priority is too low, incoming data may not be serviced quickly enough. If it is too high, excessive UART activity could interfere with more time-critical system functions.

ISR Execution Time
#

The interrupt routine should be as short and deterministic as practical.

Long-running ISRs can increase interrupt latency for other devices and make overall system behavior less predictable.

Task Synchronization
#

An ISR often needs to notify a task that new data is available.

This can be implemented using synchronization mechanisms supported by VxWorks, such as semaphores or other event/signaling facilities appropriate to the driver architecture.

The important principle is:

ISR = capture + signal

Task = parse + process

Atomicity
#

Shared indices, state flags, and counters accessed from both interrupt and task contexts must be updated safely.

Whether an operation is naturally atomic depends on the processor architecture and data type.

For more complicated shared-state updates, appropriate synchronization or interrupt-safe techniques are required.

Buffer Overflow
#

At high baud rates, data can arrive faster than the application can consume it.

For example, an 8-N-1 UART frame normally uses 10 bits on the wire for each 8-bit payload byte. At 115200 baud, the theoretical maximum payload rate is therefore approximately:

$$ \frac{115200}{10} \approx 11520\ \text{bytes/s} $$

Actual throughput can be lower depending on protocol framing, driver behavior, flow control, and system load.

The receive buffer must therefore be sized according to the maximum expected burst and the worst-case time during which the consumer task may be unable to process incoming data.

🧪 Testing UART Communication
#

UART testing should cover much more than a simple “Hello, UART” message.

A robust test plan should include:

Test Purpose
Basic TX Verify outgoing data
Basic RX Verify incoming data
Loopback Validate both directions
Baud-rate variations Verify configuration
Parity errors Test error detection
Framing errors Test malformed serial input
Overrun conditions Validate buffering
High-rate traffic Measure sustained performance
Interrupt load Evaluate latency and stability
Reset/restart Verify driver recovery
Cable/device disconnect Validate failure handling

Loopback Testing
#

In a simple loopback configuration, transmitted data is connected back to the receive path.

The test can follow:

Application
    ↓
UART TX
    ↓
Loopback
    ↓
UART RX
    ↓
Application

The application can compare transmitted and received data to verify basic functionality.

Stress Testing
#

Testing should also include sustained or bursty traffic.

For example, send continuous data while monitoring:

  • CPU utilization
  • Receive-buffer occupancy
  • Packet or frame loss
  • Interrupt rate
  • Task scheduling
  • Error counters
  • End-to-end latency

This is especially important for real-time applications where UART traffic competes with other tasks and interrupts.

🐛 Debugging UART Problems
#

When UART communication fails, the problem can originate from hardware, configuration, the BSP, the driver, or the application.

A systematic debugging sequence helps narrow the fault.

1. Check the Physical Connection
#

Verify:

  • TX/RX wiring
  • Ground connection
  • Signal levels
  • Connector pinout
  • Cable configuration
  • External transceivers

A software configuration cannot compensate for incorrect electrical wiring.

2. Verify UART Parameters
#

Confirm that both endpoints use the same:

Baud rate
Data bits
Parity
Stop bits
Flow control

A single mismatch can produce unreadable data or intermittent failures.

3. Verify the Device Path
#

Confirm that the expected VxWorks device exists and can be opened successfully.

int fd = open("/tyCo/0", O_RDWR);

if (fd < 0)
{
    perror("open");
}

If the device cannot be opened, investigate the BSP and driver before debugging application logic.

4. Inspect Driver and Hardware Status
#

Where supported, inspect UART status and error information.

Useful indicators include:

  • RX overrun
  • Framing errors
  • Parity errors
  • TX state
  • RX state
  • Interrupt status

5. Use the VxWorks Shell
#

The VxWorks shell can be valuable during development for inspecting tasks, invoking functions, checking system state, and interacting with the target.

A shell-based debugging workflow can establish whether the problem is present at the device layer before introducing application-specific code.

6. Add Structured Logging
#

Logging should identify meaningful state transitions without overwhelming the real-time system.

For example:

UART opened
UART configured: 115200 8-N-1
RX interrupt enabled
RX overflow detected
Frame received
Protocol error
UART recovered

For high-rate systems, avoid printing every received byte from the ISR. Logging at that level can substantially distort timing behavior.

🧰 A Simple UART Communication Example
#

The following example combines the basic pieces of a file-based UART application:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <termios.h>

int main(void)
{
    int fd;
    struct termios options;
    const char *message = "Hello, VxWorks UART!\r\n";
    char buffer[256];

    fd = open("/tyCo/0", O_RDWR | O_NOCTTY);

    if (fd < 0)
    {
        perror("Failed to open UART");
        return -1;
    }

    if (tcgetattr(fd, &options) != 0)
    {
        perror("tcgetattr");
        close(fd);
        return -1;
    }

    /*
     * Configure an 8-N-1-style link.
     * Use the baud-rate constant supported
     * by the target BSP/driver.
     */
    options.c_cflag &= ~(PARENB | PARODD | CSTOPB | CSIZE);
    options.c_cflag |= CS8 | CREAD | CLOCAL;

    options.c_iflag = 0;
    options.c_oflag = 0;
    options.c_lflag = 0;

    if (cfsetispeed(&options, B115200) != 0 ||
        cfsetospeed(&options, B115200) != 0)
    {
        perror("Failed to set baud rate");
        close(fd);
        return -1;
    }

    if (tcsetattr(fd, TCSANOW, &options) != 0)
    {
        perror("tcsetattr");
        close(fd);
        return -1;
    }

    if (write(fd, message, strlen(message)) < 0)
    {
        perror("UART write failed");
        close(fd);
        return -1;
    }

    ssize_t received = read(fd, buffer, sizeof(buffer) - 1);

    if (received > 0)
    {
        buffer[received] = '\0';
        printf("Received: %s\n", buffer);
    }
    else if (received < 0)
    {
        perror("UART read failed");
    }

    close(fd);

    return 0;
}

The example is intentionally simple. A production UART subsystem may require nonblocking I/O, ring buffers, task synchronization, protocol framing, timeout handling, error recovery, and interrupt-driven reception.

✅ Conclusion
#

UART remains one of the most useful communication interfaces in embedded systems because of its simplicity, low cost, and broad hardware support.

VxWorks 7 provides the real-time infrastructure needed to build reliable UART applications, while the BSP and serial driver determine how the specific hardware is exposed to the operating system.

A practical UART implementation typically follows this sequence:

  1. Select and configure a suitable BSP.
  2. Ensure the UART driver is included in the VxWorks image.
  3. Open the correct device.
  4. Configure baud rate, data bits, parity, stop bits, and flow control.
  5. Use read() and write() for basic communication.
  6. Add buffering and interrupts when responsiveness or throughput requires them.
  7. Keep interrupt handlers short and move complex processing into tasks.
  8. Test normal traffic, error conditions, buffering limits, and recovery behavior.
  9. Use the VxWorks shell, driver diagnostics, and structured logging to isolate problems.

The most important lesson is that UART programming is not just about moving bytes between two pins. In a real-time VxWorks application, reliable serial communication depends on the entire path—from hardware configuration and BSP support to driver behavior, interrupt latency, buffering, task scheduling, and error handling.

Once those pieces are designed together, UART becomes a predictable and dependable communication channel for embedded control, diagnostics, telemetry, and device integration.

Related