Example Code

Serial Protocol Communication Reference Design for Digital Waveform Devices

Products and Environment

This section reflects the products and operating system used to create the example.

To download NI software, including the products shown below, visit ni.com/downloads.


  • Digital Waveform Device
  • PXI Digital Waveform Instrument

Code and Documents




This tutorial and its attached example programs explain how to configure NI waveform-based digital I/O devices to communicate with serial protocol interfaces using the NI-HSDIO or NI-DAQmx driver software.


Serial protocols are prevalent in modern electronics systems across many industries and applications. The need to emulate and test these systems has introduced a need to communicate with devices and subsystems through serial protocols. Classically, dedicated hardware was implemented to drive and receive messages with protocol-specific encoding and timing. However, modern waveform-based devices with high-speed sample clocks are capable of accurately emulating protocol buses for the purposes of communication and parametric test. This article discusses the configuration of waveform-based hardware for protocol communication.

Theory of Operation

For the purpose of this discussion, waveform-based hardware can be defined as a device that generates or acquires digital patterns (waveforms) using a memory or FIFO space and a sample clock. All NI High-Speed Digital I/O devices and many NI DAQ devices are examples of this type of hardware. A pattern of digital samples is created as a digital waveform, which is stored in a memory buffer -- usually a bank of memory on the I/O device. A sample clock, generated by either an onboard oscillator or a counter/timer circuit, is used to generate the digital pattern with deterministic timing at a specified rate.

This technology can be utilized to emulate low-speed serial protocol signals using oversampling. Oversampling is a method of generating or acquiring signals using a sample clock that runs at an integer multiple of the signal frequency. In terms of digital communication, the oversampling technique is applied to the protocol waveform. For example, a waveform that is oversampled at 2 MHz allows the smallest time separation between any two edges defined in the waveform to be 500 ns. If it is oversampled at 4 MHz, 8 MHz, 10 MHz, etc., the precision of that edge definition is increased accordingly. When generating a serial protocol waveform, the required rate of the oversample clock is set by the precision with which that protocol's timing parameters must be defined. This makes low-speed protocols ideal for the oversampling method.

Using a waveform-based device to emulate serial transactions has a major advantage in its reusability. A single device can be used to emulate several different protocols and communicate directly with multiple types of sensors, instruments, or DUTs within a single application, merely by generating a different waveform from the device.

Programming Method

The basic approach to using a waveform-based device for serial protocol communication is:

1. Generate an oversample clock
This may be done using an onboard oscillator and a divide-down sampler, or it may be done using a counter/timer circuit. Either approach can be configured through the device's driver API.

2. Build an output waveform
Creating a serial protocol waveform using oversampling can be a complex task. Protocol signals are normally created using a state machine implemented in hardware or firmware embedded in the I/O device. A software tool used to emulate device behavior must account for all the logic transitions and custom timing normally built into that state machine.  It must also account for interactions between bus devices that are not defined by the protocol standard. Commonly, the more strictly-defined the protocol, the easier it is to design a software tool to build a waveform for that protocol.

NI Systems Engineering has developed Reference Libraries for some commonly used low-speed serial protocols. These components should be studied as examples of how to create a fast, efficient software API for generating any serial protocol waveform. They can also be directly used in applications to communicate with these protocols:

  • Serial Peripheral Interface (SPI)
    SPI is a four-wire, full-duplex bus designed for communication and control in integrated circuits. Three-, two-, and one-wire variations exist, but the four-wire variant is most common.
  • Standard Boundary-Scan protocol (IEEE 1149.1, JTAG)
    JTAG is an IEEE-standardized method of controlling boundary-scan devices for in-system programming and in-circuit test.
  • Inter-Integrated Circuit protocol (I2C)
    I2C is a two-wire communication and control bus used in integrated circuits and subsystems. It runs in half-duplex mode, requiring a bidirectional I/O driver on one of the

3. Generate the waveform while acquiring the slave device's response
Using the device's driver API, generate the protocol waveform and simultaneously acquire the slave's response using the same oversample clock as the generated data.

4. Parse the response data for desired information
The acquired response data can be parsed to extract protocol-specific information, such as a message from the slave device, a CRC check, or an acknowledgement from the slave. This method of extracting information from a raw waveform is an example of "bit-banging". 

The flexibility of this approach makes it very attractive for many applications. It is, however, a method of emulating a custom hardware design, so the tradeoffs should be considered. The first such tradeoff is shown in step 4 above, where input data must be parsed to obtain meaningful information. This process is almost universally different for every application, and so a custom function is usually written for each implementation to provide maximum efficiency.

The second tradeoff is in overall system throughput. Although a single interaction on the bus runs at full rate by sampling the stored waveform directly, the analysis of the interaction and the queuing of a second interaction requires processing on the host CPU or controller. The time required can be on the order of 10's or 100's of microseconds. As a result, when an error condition occurs on the bus -- for example, a "Not-Acknowledge" is signaled in I2C, or a slave device fails to respond when addressed -- this situation will not be recognized and handled for a very long time, in terms of the serial bus clock. (For all but the lowest-speed buses, the system response to a bad packet or error condition will be orders of magnitude longer than the period of the sample clock.)

For the same reason, waveform devices are not suited to acting as a slave on most serial buses. Slave devices normally listen to the bus and respond immediately to the master device when addressed. The low throughput of a host-side bit banging emulation makes it difficult to configure waveform-based devices to respond to serial addressing mechanisms immediately.

For applications whose purpose it is to simply communicate with a slave device, these tradeoffs are not a limiting factor. Since interactions on most low-speed serial buses are initiated and controlled by a master device, the application software can respond to each packet (or otherwise-defined bus interaction) at its leisure: the slave devices will wait until the master is ready.

Reference Applications

There are several NI devices capable of performing clocked digital I/O operations using waveform data. These devices come from many product families which use multiple driver APIs. The following sections review template applications that explain how to configure and run devices from each family for serial protocol communication. Each template groups device families according to their capabilities and associated driver software. Example VIs based on the templates are provided in the Downloads section of this article.

Use the following table of contents to jump to your hardware's section:

  1. NI-HSDIO devices
  2. NI-DAQmx devices

NI-HSDIO Devices

The NI-HSDIO driver is designed to control high-speed digital I/O modular instruments. These devices are designed with impedance-matched termination, several types of triggers and exported events, and a high-speed onboard oscillator that can be divided to achieve sampling rates up to 200 MHz. There is a set of "core" features that can be used with NI-HSDIO based device to achieve serial protocol communication for most buses. A few extra "advanced" features available on some of these devices are useful for achieving faster system throughput and for communicating on bidirectional (half-duplex) buses.

Using Core Features Only 

All 654x, 655x, and 656x devices share a common set of features that can be configured for serial communication using the same function calls. These features configure a device to generate a waveform using actively-driven outputs on dedicated output pins while simultaneously acquiring a waveform on dedicated input pins. (The generated data can be acquired on the dedicated output pins as well; this can be useful for correlating events in the input and output waveforms.)

The initial configuration of the device is performed only once. The following features are configured in this stage:

  1. Assign channels (pins) to each of the generation and acquisition engines. As mentioned previously, output channels can be assigned to the acquisition engine in addition to the generation engine. This will result in acquiring a waveform that contains all bus channels' data, both input and output. When parsing this data, the input and output events (edges, bits, packets, etc.) can be correlated to create a more efficient or robust bit-banging algorithm.
  2. Use the onboard oscillator (On Board Clock) to generate an oversample clock. Configure both engines to use this clock so they sample output and input data simultaneously.
  3. Configure the initial and idle states of the output channels so the serial bus will not be "in use" when the device isn't actively emulating a master node.
  4. Use the generation engine's Data Active event to trigger the acquisition engine. This ensures that both engines start simultaneously, on the sample oversample clock edge. The trigger can be routed internally by configuring the acquisition engine's trigger source as "DelayedDataActiveEvent".
  5. Optionally apply an artificial delay to the internal routing of the Data Active event, so it arrives at the acquisition engine's trigger circuitry at the same time that the generation engine's output data arrives on the pin circuitry. The size of this delay, measured in periods of the oversample clock, will vary with the clock rate and must be hand-tuned.

Once the device has been configured, common set of functions can be called in sequence to load a waveform, generate it, and retrieve the acquired data from the device. This sequence of calls can be made repeatedly for subsequent bus interactions.

  1. Load the output waveform into the device's generation memory and configure a buffer of equal size in the acquisition memory.
  2. Start the acquisition engine. It will run idle while waiting for a trigger from the generation engine (via the Delayed Data Active event).
  3. Start the generation engine. It will trigger the acquisition engine when it starts.
  4. Wait until the acquisition engine is finished acquiring all samples, then fetch them from the device's memory.
  5. Stop the generation and acquisition engines. This is necessary to be able to perform the next step.
  6. Delete the waveform from generation memory to avoid a memory leak across multiple executions of this call sequence. (With each call, a new waveform is created in generation memory at the beginning of the sequence.) The acquisition waveform was deallocated automatically when its data was fetched from the device.

The waveform retrieved from the acquisition engine can now be parsed to retrieve pertinent protocol-specific information.

Using Advanced Features

Some NI devices (including the 655x series) feature advanced capabilities which expand the set of protocols that can be emulated and increase the throughput of the emulating application. 

Per-Cycle Tristate

Some protocols, such as I2C, minimize wire count by transferring data in half-duplex. This requires bidirectional communication across some pins, and often the role of driving the pin is transferred between master and slave within a single clock period. NI devices that support Per-Cycle Tristating can deactivate their line drivers within a single clock cycle to accommodate this operational requirement.

An additional benefit of per-cycle tristating is that the device can be used on buses that require passively-driven outputs, such as open-drain, wired-OR and open-collector drivers. By using "Z" as the high state and "0" as the low state, a waveform can be written that causes the device to pull the line low for a '0' bit and let it float for a '1' bit. This is a requirement for the I2C and SMBus protocols.

Because of this requirement, only devices with the per-cycle tristate feature can be used to emulate I2C communication as described in this article.

Hardware Compare

The Hardware Compare feature is another useful advanced feature of NI-HSDIO based devices. The hardware compare feature allows the application software to fetch only a record of interesting samples from device memory, instead of all acquired samples. This means that much less time is spent transferring data from the device to the host CPU. It also means that the parsing function which analyzes the acquired waveform to extract protocol information has less work to do and can be designed more efficiently. As a result, using hardware compare in a serial protocol application increases overall system throughput at runtime.

Programming with Advanced Features

To activate Per-Cycle Tristating, the waveform data generated by the device must be encoded with the "Z" state where appropriate. Likewise, the Hardware Compare states "L", "H", and "X" must be used in the waveform to make use of that feature. Hardware Compare state encoding is used in all of the SDW, JDW, and IDW reference libraries linked above, and open-collector output levels ("Z" and "0") are used in the IDW library.

The device programming flow differs only slightly when utilizing Hardware Compare:

  1. During the Initialization sequence, configure the device's Hardware Compare engine to operate in "Stimulus and Expected Response" mode.
  2. During the Runtime sequence, fetch the sample errors instead of the acquired waveform. This data set will be substantially smaller than the raw waveform data, and it will only contain samples that have been flagged for comparison.

Parsing the sample errors is much more efficient than parsing the raw waveform. A template algorithm for sample error parsing is provided for each protocol in the attached example programs.

NI-DAQmx Devices

The NI-DAQmx driver supports several families of products. Some of these products have digital I/O buffers that can be used with a sample clock to emulate a serial bus interaction. These devices do not generally support oversample clock rates as high as the NI-HSDIO based devices, nor do they support any advanced NI-HSDIO features. (As such, they cannot be used to emulate bidirectional bus transactions in the manner discussed here.)

There are two primary methods of generating an oversample clock for an NI-DAQmx based device. Some devices provide an onboard oscillator that can be used to generate the clock internally, while other devices provide counter/timer circuits for generating a clock signal.

Using an Internal Sample Clock

The 6535/6/7 devices have an onboard oscillator and divide-down circuitry available for generating sample clocks. This makes their programming flow very similar to the core NI-HSDIO flow.

The initial configuration sequence is performed only once:

  1. Assign lines to both the Digital Output (DO) and Digital Input (DI) tasks. Configure the DO task to use "one channel for all lines", which allows a single Digital Waveform to define all output pins' data. (This is compatible with the output of the IDW, JDW, and SDW libraries.)
  2. Initialize the device's output pins to the appropriate states. This is done by writing a single sample to the DO task and letting it run automatically. The states defined in the sample will be held by the output drivers after the write operation completes.
  3. Configure the DI task to listen for a Start trigger from the DO task. For 6535/6/7 devices, use the DO task's Data Active Event as the trigger, just as with an NI-HSDIO based device. The trigger cannot be routed internally on 6535/6/7 devices, so use a PFI pin -- PFI1 is used in the examples -- to route the event signal to the trigger input circuitry.
  4. Disable buffer regeneration in the DO task. This prevents old data from being generated by the device accidentally.

Once the device has been configured, common set of functions can be called in sequence to load a waveform, generate it, and retrieve the acquired data from the device. This sequence of calls can be made repeatedly for subsequent bus interactions.

  1. Configure the sample timing and waveform buffer size for each task.
  2. Write the waveform into the DO task buffer. Do not let the task start itself automatically.
  3. Start the DI task. It will run idle until it receives a trigger from the DO task. (This trigger signal is the Data Active event.)
  4. Start the DO task. It will issue the Data Active event when it starts, triggering the DI task as well.
  5. Wait for the DI task to complete, then read the acquired waveform data from the task.
  6. Stop both the DO and DI tasks.

The waveform retrieved from the acquisition engine can now be parsed to retrieve pertinent protocol-specific information.

Using Correlated DIO

NI DAQ devices do not generally have an onboard oscillator used to run hardware-timed DIO tasks. However, some devices, like the M-series (62xx), are capable of correlating their DI and DO tasks to an externally generated clock signal. A counter/timer on the device can be used to generate an oversample clock, which can then be internally routed to the DI and DO tasks.

The device programming flow for M-series devices only differs slightly from the flow for internally clocked devices. The initialization sequence has two differences:

  1. A Counter Output (CO) task is created and configured for pulse generation with continuous timing.
  2. M-series devices do not generate a Data Active event. Fortunately, the DI and DO tasks will be sampling with a clock source that is not free-running -- the CO task -- so they will not start until the clock does. This fact can be used to ensure a simultaneous start without the need for a trigger signal.

The runtime sequence of function calls is nearly identical, except that the CO task must also be managed alongside the DI and DO tasks:

  1. The counter/timer's internal output signal ("CtrXInternalOutput") is specified as the source of the oversample clock.
  2. The DI and DO tasks must be started prior to starting the CO task.
  3. Stop the CO task when stopping the DI and DO tasks (in any order).

Support and Discussion

This tutorial and the attached Reference Application were created by the NI Systems Engineering group.

Example code from the Example Code Exchange in the NI Community is licensed with the MIT license.


I  test serial_wfm_1_0_1_lv86.zip  on my PXIe-6556 , it work well for single a byte reading form my I2C Device, but If i read continuous bytes, data are wrong.  I did not know why?  Any one can help to answer this problem?  I get another example VI form https://www.dssz.org/1860279.html , but the waveform pattern generator VI are not compatible to integrate them for byte and word access I2C device, I hope someone have better suggestion.



Hi , I keep getting an error while trying to use th downloadable examples.
the following error Message appears:

"Specified read or write operation failed, because the number of lines in the data for a channel does not match the number of lines in the channel.
If you are using the Digital Waveform datatype, make sure the number of lines in the digital waveform matches the number of lines in the channel. If you are using boolean data, make sure the array dimension for lines in the data matches the number of lines in the channel.

Number of Lines in Channel: 1
Number of Lines in Data: 3

Task Name: _unnamedTask<1C>"