↓ Skip to main content

VxWorks 6.6 VxBus CAN Driver: SJA1000T Development Guide

VxWorks 6.6 VxBus CAN Driver: SJA1000T Development Guide

📘 Introduction
#

Controller Area Network (CAN) is a widely used communication bus for embedded control systems. Its multi-master arbitration, built-in error detection, and differential signaling make it suitable for automotive electronics, industrial automation, instrumentation, and other applications requiring reliable communication between distributed controllers.

VxWorks 6.6 provides the VxBus device-driver framework, which allows hardware drivers to be registered as components and integrated into a configured operating-system image. Compared with older BSP-specific driver implementations, VxBus provides a more modular approach to device discovery, initialization, resource management, and driver integration.

This guide describes how to develop a CAN controller driver for the NXP SJA1000T under VxWorks 6.6 using Workbench 3.0. It covers the driver structure, build workflow, BSP resource configuration, controller initialization, transmit and receive operations, configuration interfaces, and hardware debugging.

The implementation is based on a PCI-connected CAN communication board whose SJA1000T controller is integrated through the target platform’s hardware interface. The examples use the historical VxWorks 6.6 driver model, so their registration structures, resource definitions, and build commands should not be assumed to work unchanged with VxWorks 7 or newer VxBus releases.

For related background, see the VxBus SJA1000T CAN Driver Development Guide and the CAN Programming Under VxWorks guide, which cover controller integration and application-level CAN communication.

🏗️ Understanding the VxBus Driver Architecture
#

Workbench 3.0 and VxWorks Image Projects
#

Workbench 3.0 is the integrated development environment used with VxWorks 6.x. It supports several project types, including:

  • VxWorks Image Projects.
  • Boot Loader and BSP Projects.
  • VxWorks Real-Time Process projects.
  • VxWorks Downloadable Kernel Module projects.

A VxBus driver is generally integrated into a VxWorks Image Project through the kernel component configuration.

The driver source code is compiled into a library or other build artifact, its component description exposes the driver to Workbench, and the selected image configuration determines whether the driver is included in the final kernel image.

This modular approach reduces the need to embed all device initialization directly in BSP source files. It also allows driver components to be enabled or disabled through the project configuration.

VxWorks 5.5 used Tornado 2.2 and did not provide the same VxBus driver model. Legacy drivers often relied more heavily on BSP-specific initialization and registration, making reuse across different boards more difficult.

Driver Source Structure
#

A typical VxWorks 6.6 VxBus driver package contains several files.

File Purpose
README Documents the driver, supported hardware, configuration, and usage
Makefile Defines compilation rules and build dependencies
driverName.cdf Describes the driver component, dependencies, directory placement, and Workbench integration
driverName.dr Provides the VxBus registration function or related registration source
driverName.dc Declares the registration function for the component framework
driverName.c Implements the driver data structures, lifecycle functions, and hardware operations

The exact file naming conventions depend on the installed VxWorks release and the component template being used.

The component description file is particularly important. If its component identifiers, dependencies, directory entries, or registration references are incorrect, Workbench may not display the driver in the expected component category.

Core Driver Data Structure
#

A VxBus driver maintains a device-specific structure containing the state required to operate the hardware.

The following example shows the basic structure of an SJA1000T driver using the naming convention from the original implementation.

typedef struct can1000tHwmonCtrl
{
    VXB_DEVICE_ID pDev;

    /*
     * Add controller-specific state here:
     * register mappings, interrupt information,
     * receive buffers, synchronization objects,
     * configuration values, and error counters.
     */
} CAN1000T_HWMON_CTRL;

The VXB_DEVICE_ID handle connects the driver to the VxBus device instance. The remaining fields should describe the resources and operational state of the individual controller.

A production implementation should avoid keeping mutable state in unprotected global variables when multiple controller instances can execute concurrently.

VxBus Lifecycle Functions
#

The driver implements functions that are called during different stages of device initialization and connection.

LOCAL void can1000tHwmonInstInit(VXB_DEVICE_ID pDev);
LOCAL void can1000tHwmonInstInit2(VXB_DEVICE_ID pDev);
LOCAL void can1000tHwmonInstConnect(VXB_DEVICE_ID pDev);

These functions correspond to the driver lifecycle callbacks used by the VxBus version in this example.

Their responsibilities should be separated according to the initialization guarantees available at each stage. For example, early initialization may establish instance state, a later stage may acquire hardware resources, and the connection stage may complete initialization that depends on the bus or interrupt infrastructure.

The exact ordering and permitted operations must be verified against the VxWorks 6.6 VxBus documentation for the selected BSP.

Declare the Driver Methods
#

The driver exposes methods that define how other components interact with the CAN controller.

The original implementation uses method names based on a hardware-monitoring driver template:

LOCAL device_method_t can1000tHwmonMethods[] =
{
    DEVMETHOD(HwmonSendData, can1000tHwmonSendData),
    DEVMETHOD(HwmonRecvData, can1000tHwmonRecvData),
    DEVMETHOD_END
};

For a CAN driver, the method identifiers and function signatures must match the interfaces expected by the driver class or by the application-facing components.

If the HwmonSendData and HwmonRecvData identifiers originate from a temperature-sensor template, they should be replaced or adapted to reflect the intended CAN interface. The example preserves them only to illustrate the registration mechanism.

The core driver functions can include:

  • CanControllerInit() for controller initialization.
  • CanControllerTransmit() for transmitting CAN frames.
  • Can1000tHwmonRecvInt() for interrupt-driven receive processing.
  • can1000tHwmonRecvData() for delivering received frames to the caller.
  • hwmonIoctl() or an equivalent configuration entry point.

Register the Driver with VxBus
#

The driver associates its lifecycle functions and methods with a registration structure.

A simplified declaration following the historical VxWorks 6.6 pattern is:

LOCAL struct drvBusFuncs can1000tHwmonFuncs =
{
    can1000tHwmonInstInit,
    can1000tHwmonInstInit2,
    can1000tHwmonInstConnect
};

LOCAL DRIVER_REGISTRATION can1000tHwmonDevRegistration =
{
    NULL,                       /* pNext */
    VXB_DEVID_DEVICE,           /* devID */
    VXB_BUSID_PLB,               /* busID */
    VXBUS_VERSION_3,             /* bus version */
    "can1000t",                  /* driver name */
    &can1000tHwmonFuncs,         /* lifecycle functions */
    can1000tHwmonMethods,        /* driver methods */
    NULL                         /* probe callback */
};

void can1000tHwmonRegister(void)
{
    vxbDevRegister(
        (struct vxbDevRegInfo *)&can1000tHwmonDevRegistration
    );
}

This snippet illustrates the registration pattern described for the target environment. Before compiling, verify the exact registration type, structure layout, bus identifier, and method signatures against the VxWorks 6.6 headers and existing working VxBus drivers.

The VXB_BUSID_PLB value should only be used if it matches the actual attachment model of the device. A PCI-connected device and a device connected directly to a processor-local bus can require different bus identifiers and resource-discovery procedures.

🔨 Compiling a VxBus Driver in VxWorks 6.6
#

The historical build process involves preparing the VxWorks component catalog, building the component infrastructure, and compiling the third-party driver for the target CPU and compiler.

The commands below reflect the legacy environment described in this guide. They are not interchangeable with current VxWorks 7 SDK build workflows.

Initialize the Development Environment
#

Open a Windows command prompt and initialize the VxWorks 6.6 environment:

wrenv.exe -p vxworks-6.6

Replace subsequent path examples with the actual installation directory.

Regenerate the VxBus Command-Line Component File
#

Navigate to the component source directory:

cd installDir\vxworks-6.x\target\config\comps\src\hwif

Run the build command:

make vxbUsrCmdLine.c

The historical procedure specifies deleting an existing generated vxbUsrCmdLine.c file before running this command when regeneration is necessary.

Because this is a generated source file, back up any locally modified content and confirm the build instructions for the installed VxWorks release before removing it.

Rebuild the Kernel Component Catalog
#

Navigate to the VxWorks component directory:

cd installDir\vxworks-6.x\target\config\comps\vxWorks

The original workflow removes the generated CxrCat.txt file and rebuilds the component catalog:

del CxrCat.txt
make

Run these commands only in the appropriate VxWorks installation or controlled build tree after confirming that the file is generated and can safely be regenerated. Do not remove files from a production or shared installation without first preserving local changes.

Compile the Third-Party Driver
#

Navigate to the third-party driver directory:

cd installDir\vxworks-6.x\target\3rdparty\vendor\driver

Compile the driver using the CPU and compiler configuration appropriate to the selected BSP:

make CPU=cpuName TOOL=tool

For example, a PowerPC 32-bit BSP using the specified DIAB toolchain may use:

make CPU=PPC32 TOOL=sfdiab

A GNU compiler configuration may instead use the tool identifier supported by the installed VxWorks release.

The CPU and TOOL values must match the target architecture and compiler toolchain. The names shown above are illustrative examples from the historical build environment.

Verify the Build Artifact
#

The original workflow places compiled libraries in a target-specific location similar to:

installDir\vxworks-6.x\target\lib\ppc\PPC32\common\

Confirm the actual path and output name for the selected architecture and toolchain.

After compilation, create a VxWorks Image Project in Workbench 3.0, select the appropriate BSP, and locate the driver under the applicable kernel component category.

If the driver does not appear, inspect the component description file, registration declarations, compilation output, and component dependencies before attempting to add it to the image.

Some projects can also compile the driver independently from its source directory:

make CPU=PPC32 TOOL=sfdiab

This approach is useful during driver development because it allows the source code to be rebuilt without manually rebuilding unrelated application code. The final image must still be configured to include the required driver component.

🔌 Understanding the SJA1000T CAN Controller
#

The SJA1000T supports BasicCAN and PeliCAN operating modes. The selected mode affects the available controller features, register interpretation, and supported CAN frame formats.

BasicCAN and PeliCAN
#

BasicCAN provides the controller functionality associated with the earlier PCA82C200-compatible operating model.

PeliCAN provides the extended feature set required for CAN 2.0B operation, including support for extended identifiers and additional controller status and error information.

The implementation described here uses PeliCAN mode.

Before writing register-level operations, consult the SJA1000T data sheet and identify the controller’s memory-mapped or I/O-mapped register layout for the board. The register address calculations may depend on how the board’s FPGA, address decoder, or PCI interface maps the controller registers.

The hardware integration must correctly identify the base address, register stride, access width, and byte ordering expected by the controller.

🧱 Add Hardware Resources to the BSP
#

The driver needs access to the CAN controller’s register base address, interrupt configuration, and other platform resources.

In the historical VxWorks 6.6 environment, the BSP’s hwconf.c file provides hardware configuration resources for the VxBus instance.

Configure Interrupt Resources
#

The original implementation assigns a shared external interrupt source to the CAN controller instances.

An illustrative configuration is:

#ifdef DRV_HWMON_ZKHXET_CAN1000T

{
    EPIC_VEC_EXT_IRQ0,
    "can1000t",
    0,
    0
},

{
    EPIC_VEC_EXT_IRQ0,
    "can1000t",
    1,
    0
},

#endif /* DRV_HWMON_ZKHXET_CAN1000T */

The interrupt vector, driver name, instance index, and interrupt flags must match the actual BSP configuration and the resource conventions expected by the installed VxWorks release.

If multiple controllers share one interrupt line, the handler must identify which controller or controllers generated the event before acknowledging their interrupt status.

Configure Register and Device Resources
#

A typical legacy resource declaration has the following form:

#ifdef DRV_HWMON_ZKHXET_CAN1000T

const struct hcfResource can1000t1Resources[] =
{
    {
        VXB_REG_BASE,
        HCF_RES_INT,
        { (void *)0xEE000000 }
    },

    {
        "irq",
        HCF_RES_INT,
        { (void *)EPIC_VEC_EXT_IRQ0 }
    },

    {
        "busno",
        HCF_RES_INT,
        { (void *)0 }
    }
};

#define can1000t1Num NELEMENTS(can1000t1Resources)

#endif /* DRV_HWMON_ZKHXET_CAN1000T */

The address 0xEE000000 is an example from the original implementation, not a portable SJA1000T base address. Replace it with the address assigned by the target hardware design.

Likewise, the resource names, resource types, and values must be consistent with the driver’s resource lookup code. Verify them against examples supplied with the target BSP.

If the PCI board exposes multiple CAN controllers through an FPGA or bridge, the driver must also account for the mapping from each logical channel to the corresponding controller register window.

Build and Boot the Updated BSP Image
#

After modifying the hardware configuration:

  1. Create or update the appropriate Boot Loader/BSP Project in Workbench 3.0.
  2. Select the target BSP and compile the updated configuration.
  3. Deploy the updated boot image according to the board’s documented procedure.
  4. Boot the target with the VxWorks image containing the driver.
  5. Run VxBusShow from the target shell to inspect registered devices and drivers.

If the driver is missing from the expected output, check the registration function, component configuration, driver name, resource declarations, and boot image.

The presence of the driver name in the source code does not guarantee that its initialization succeeded. Review boot-time logs and initialization status to determine whether the driver registered successfully and whether the hardware was identified.

⚙️ Implement the SJA1000T Driver Functions
#

Once the component has been registered and its hardware resources are available, implement the controller operations.

Controller Initialization
#

Initialization begins by placing the SJA1000T into reset mode. Certain configuration registers may only be written while the controller is in the permitted configuration state.

A typical sequence is:

  1. Enter reset mode through the Mode Register (MOD).
  2. Configure the clock-divider settings and select PeliCAN mode.
  3. Set the required operating and filtering modes.
  4. Initialize acceptance-code and acceptance-mask registers.
  5. Configure the bus-timing registers for the selected bit rate.
  6. Configure the required interrupt sources and clear pending status where appropriate.
  7. Enter normal operating mode after all configuration has completed successfully.

The exact order and register values depend on the SJA1000T data sheet, the board’s clock configuration, and the supported bit rates.

The original implementation uses a function similar to:

STATUS CanControllerInit(VXB_DEVICE_ID pDev);

The function should validate its device context, access the controller through the proper mapped register interface, and return an error if initialization fails.

Acceptance filters may initially be configured to receive a broad range of identifiers during development. For production use, configure filters according to the application’s traffic requirements and ensure that the mask semantics match the chosen controller mode.

CAN Data Transmission
#

The original implementation uses polling for transmission. Before loading a new frame, the driver checks the controller’s status register to confirm that the transmit buffer is available.

The transmission function is represented by:

STATUS CanControllerTransmit(
    VXB_DEVICE_ID pDev,
    unsigned char *txData,
    int len
);

The driver should validate the frame length and format before writing to the controller.

Relevant frame parameters include:

  • Standard or extended identifier.
  • Identifier value.
  • Data frame or remote frame where supported.
  • Payload length.
  • Payload bytes.

The implementation must respect the maximum payload size supported by the selected mode. Classical CAN data frames can carry up to eight data bytes; CAN FD requires a different controller and driver capability set.

Polling should be bounded by a timeout. An indefinite wait for the transmit buffer can block an application task permanently if the controller encounters an error or the CAN bus is unavailable.

After a transmission request is accepted, the driver should distinguish between submitting the frame and confirming successful transmission if the controller and API expose that information.

CAN Data Reception
#

The receive implementation allocates buffering in the driver structure so that frames can be stored before an application reads them.

When a receive interrupt occurs, the ISR retrieves available frame data, stores it in the appropriate buffer or queue, and updates the corresponding state.

The original implementation uses a function similar to:

void Can1000tHwmonRecvInt(VXB_DEVICE_ID pDev);

When an application requests data, the receive function copies the available frame information to the caller:

STATUS can1000tHwmonRecvData(
    VXB_DEVICE_ID pDev,
    int *id,
    int *ext_flag,
    int *rtr_flag,
    int *time_stamp,
    int *data,
    int buf_len
);

The parameters provide the received identifier, frame-format flags, timestamp, payload, and available buffer length.

The implementation must define the timestamp’s units and reference, validate all output pointers, check the destination buffer capacity, and communicate the received payload length unambiguously.

For multichannel hardware, receive buffering must also remain isolated per controller. A frame received on one channel must never be returned as data from another channel.

If reception occurs in interrupt context, the ISR should avoid blocking operations and lengthy application processing. Buffer ownership and synchronization must follow the requirements of the execution context and the VxWorks APIs being used.

CAN Configuration and IOCTL
#

Unlike a simple byte-oriented serial interface, CAN requires configuration of frame identifiers, acceptance filters, bit timing, and controller modes.

The original implementation exposes a configuration entry point similar to:

STATUS hwmonIoctl(
    HWMON_DEV_HDR *pDevHdr,
    int func,
    int arg
);

The ioctl-style interface allows application software to request supported changes without directly accessing hardware registers.

Potential configuration operations include:

  • Setting the nominal CAN bit rate.
  • Selecting an acceptance-filter mode.
  • Configuring acceptance codes and masks.
  • Enabling or disabling supported interrupt sources.
  • Retrieving controller status and error information.

The exact command identifiers, parameter types, and return conventions are driver-specific. For a production implementation, use defined request structures or types appropriate to the target ABI instead of relying on undocumented casts or assumptions about pointer width.

Before applying a new configuration, validate the requested values and ensure the controller is placed in an appropriate mode. If reconfiguration can occur while the device is active, define how queued frames and pending interrupts are handled.

🧪 Driver Debugging and Hardware Validation
#

A controller driver needs both software-level diagnostics and direct hardware validation. A successful build proves that the source compiles; it does not prove that the controller is configured correctly or that frames are transmitted and received on the physical bus.

Prepare the Debugging Environment
#

The original development setup uses a USBCAN-2I interface and CAN transmit/receive application software. A compatible USB-to-CAN interface, CAN analyzer, or another trusted node can serve the same general purpose.

Before starting tests, verify:

  • The CAN controller is connected to the correct physical bus.
  • The transceiver and controller are powered correctly.
  • The bus wiring and termination match the network design.
  • All nodes use compatible bit timing.
  • The target image contains the correct driver and hardware configuration.

An oscilloscope can help verify electrical activity when software reports transmission attempts but no valid frames appear on the bus.

A CAN analyzer provides additional visibility into frame identifiers, payloads, error frames, bus loading, and acknowledgement behavior.

Use Diagnostic Variables and Logging
#

During early development, global diagnostic variables can help record information that is difficult to inspect directly during initialization.

Examples include:

  • Detected PCI or bus resources.
  • The selected register base address.
  • Controller initialization results.
  • Interrupt counts.
  • Receive-buffer status.
  • Error counters and timeout statistics.

The target can expose this information through an appropriate debugging interface or diagnostic function.

VxWorks’ logMsg() can be useful for recording diagnostic events in supported configurations. However, excessive logging in an ISR can significantly change timing behavior. Prefer bounded counters and deferred reporting when measuring interrupt-sensitive workloads.

Diagnose Missing or Invalid CAN Traffic
#

If frames are not transmitted or received as expected, investigate the problem layer by layer.

Controller initialization: Verify the reset state, PeliCAN mode, bit-timing registers, acceptance-filter settings, and transition into normal operation.

Hardware resources: Confirm that the register base address and interrupt resources match the selected board configuration.

Interrupt handling: Check whether the expected interrupt is generated, whether the status is read correctly, and whether the controller’s acknowledgement sequence follows the data sheet.

Device registration: Confirm that the driver is registered through VxBus and that its resource configuration is associated with the correct device instance.

Physical bus: Use a CAN analyzer or oscilloscope to determine whether valid frames are present and whether the bus exhibits errors related to timing, wiring, or termination.

This layered diagnostic approach helps distinguish software errors from controller configuration problems and electrical faults.

🐛 Common Problems and Troubleshooting
#

Workbench Cannot Add the Driver Component
#

If the driver component does not appear under the expected third-party driver category, inspect driverName.cdf.

The original debugging process identified an incorrect component directory entry as the cause. The expected historical entry is:

_CHILDREN FOLDER_3RD_DRIVERS

Verify that the component identifier, directory placement, dependency declarations, and build references all match the installed Workbench component system.

The exact hierarchy may differ across SDK versions, so use a working component definition from the same VxWorks installation as the primary reference.

VxBusShow Does Not List the Driver
#

If VxBusShow does not display the expected driver, check that:

  • The registration function is included in the image or invoked through the supported initialization path.
  • The registration structure uses the expected driver and bus identifiers.
  • The driver name is consistent across its source files and hardware configuration.
  • The relevant CDF component is enabled.
  • The driver library is linked into the correct image.
  • Resource lookup and initialization do not fail before device registration completes.

A naming mismatch can prevent the driver from matching the expected device configuration or make it difficult to locate the registered instance.

The Controller Initializes but Communication Fails
#

If the driver appears in VxBusShow but no valid frames are exchanged, check the controller’s normal operating state, bit timing, acceptance filters, register mapping, interrupt configuration, and physical bus.

Do not assume that successful VxBus registration means the CAN controller itself is fully operational. Driver registration, hardware attachment, and protocol communication are distinct stages and should be validated independently.

✅ Conclusion
#

Developing an SJA1000T CAN driver under VxWorks 6.6 requires an understanding of both the VxBus component model and the controller’s register-level behavior.

The workflow begins with a modular driver structure, including lifecycle callbacks, method declarations, registration metadata, and a component description file. The BSP then supplies the register mappings and interrupt resources needed for hardware initialization.

The driver implementation must configure the controller’s operating mode, bit timing, and acceptance filters; support reliable transmission and reception; and expose application-level configuration through a suitable control interface. Finally, hardware testing verifies that the driver works beyond the build environment.

The most important engineering considerations are:

  • Correct VxBus integration: Ensure that driver metadata, lifecycle methods, and BSP resources match the target release.
  • Correct controller programming: Follow the SJA1000T data sheet for reset, PeliCAN configuration, bit timing, and interrupt handling.
  • Safe data handling: Define buffer ownership, receive synchronization, frame validation, and bounded transmit waits.
  • Systematic debugging: Combine kernel diagnostics, VxBusShow, CAN analyzer measurements, and oscilloscope inspection to isolate faults.

The design remains useful as a reference for legacy VxWorks 6.6 deployments and for understanding how hardware-specific CAN functionality fits into a VxBus-based driver architecture. Porting it to another VxWorks release requires checking API compatibility, resource conventions, compiler assumptions, and the target BSP’s hardware initialization model.

Related