Driver Concepts Guide

Overview

At the core of Addi-Pack lies a powerful driver layer designed to manage all communication between your application and ADDI-DATA hardware — across Windows (KMDF), Linux (DKMS) and real-time environments such as IntervalZero RTX and other RTOS-based systems.

Understanding how this layer works helps you optimise performance, reliability, and responsiveness.

This section explains:

  • The difference between polling and interrupts

  • How device identification and card selection work

  • How to use Compatibility DLLs to support applications built with the older API

  • The fundamentals of timers, counters and watchdogs

Tip

Mastering these concepts is especially useful for high-performance or multi-card systems.

Driver Architecture

Addi-Pack drivers follow a two-layer architecture:

  1. Kernel Layer (Driver)

    • Handles PCI/PCIe bus communication, hardware interrupts, and DMA transfers.

    • Implemented as a KMDF driver on Windows and a DKMS module on Linux, and compatible with real-time extensions such as IntervalZero RTX and other RTOS environments.

  2. User Layer (API)

    • Provides a unified programming interface in C, C++, and Python.

    • Communicates with the driver using IOCTL calls under the hood.

    • Exposes high-level features like timers, counters, digital I/O, and event handling.

Interrupts vs Polling

Most Addi-Pack features support both polling and interrupt-driven modes.

Mode

Description

Polling

The application checks periodically for a state change. Simple to use but inefficient for high-frequency tasks.

Interrupts

The hardware notifies the application only when an event occurs. Efficient and real-time capable. Recommended for timers and counters.

Example scenarios:

  • Digital I/O: Polling is acceptable when reading every 100–200 ms.

  • Timers / Counters: Prefer interrupts to avoid missing fast events.

Why Use Interrupts?

Interrupts are essential when you need to:

  • React to fast or unpredictable events without missing them,

  • Reduce CPU usage by avoiding constant polling,

  • Handle multiple events from different subsystems (inputs, timers, counters).

Without interrupts, your application would need to poll the hardware in a tight loop — which can:

  • Miss short pulses or transitions between reads,

  • Consume unnecessary CPU cycles,

  • Become difficult to scale or integrate with other tasks.

Using interrupts ensures that your application is notified precisely when something happens, allowing you to respond immediately and efficiently.

Typical examples where interrupts are critical:

  • Timer expiration: Trigger an action exactly every N milliseconds,

  • Counter overflow: React when a threshold is reached,

  • Digital input change: Detect a rising edge on an external signal (e.g., sensor, button).

  • Digital output fault detection: Detect a power supply failure (VCC) or a short-circuit (CC) on digital outputs.

Understanding Interrupt Handling

To use interrupts, you need to:

  1. Enable device-level interrupt support

    (AddiDataEnableDeviceInterrupts(id))

  2. Configure the interrupt source For example, select which inputs or timers should trigger an interrupt.

    (AddiDataEnableTimerInterrupt(id, timer_id))

  3. Set the interrupt mask This defines which bits (channels) will be monitored by the hardware.

  4. Register a callback In interrupt mode, your application will receive a notification function to handle events.

    (AddiDataSetDeviceInterruptCallback(id, &interruptCallback))

  5. Process the event In the callback, you typically:

    • Check the cause of the interrupt (e.g., which input changed),

    • Recording the time between interrupts

    • Read values (status, input lines, timer/counter),

    • Optionally acknowledge or reset the event flag.

Note

The interrupt mask is a bitfield that selects which channels or sources are monitored.

For example, 0x00000001 monitors channel 0, 0x0000000F monitors channels 0–3.

C Example:

void my_callback(AddiDataDeviceStruct device, InterruptData data) {
   printf("[INTERRUPT] source=0x%X | mask=0x%X\n",
         data.interrupt_source, data.interrupt_mask);

   interrupt_happened_flag = 1;
}
AddiDataDeviceStruct device;
const char* id = device.id;

AddiDataEnableDeviceInterrupts(id);         // Enable global card interrupts
AddiDataSetInterruptEventForInputs(id, 0x10, RISING_EDGE);  // Monitor input 4
AddiDataSetInterruptLogicForPort(id, 0, OR);
AddiDataSetDeviceInterruptCallback(id, &my_callback);
AddiDataEnableDigitalInterrupt(id);

Tip

All information about the device is stored in the AddiDataDeviceStruct structure, which is filled by AddiDataGetDeviceList().

Always check this structure before enabling interrupts to know:

  • Which inputs/ports are available,

  • The supported interrupt mask (e.g., 0xFFFF, 0xFFFF0, etc.),

  • Whether the card supports digital events or not.

Device Identification

Each ADDI-DATA card has a unique device ID automatically assigned at system startup. You can list all connected cards and retrieve their properties using the `get_device_info` sample.

CLI Example:

$AddiPack_ROOT/samples/get_device_info/build/get_device_info

Information shown includes:

  • Device name and model (e.g. apcie1500)

  • PCI bus and slot

  • Supported functions (digital I/O, timer, counter, watchdog)

  • Driver version and status

This information can be used to:

  • Select a specific card in multi-device systems

  • Confirm that all driver modules are properly loaded

Want to configure multiple timers with different interrupts?

TIMEBASE_UNITS timebase = MILLISECOND;
AddiDataInitTimer(&device, 0, timebase , 100, 0);
AddiDataInitTimer(&device, 1, timebase , 250, 0);

AddiDataEnableTimerInterrupt(&device, 0);
AddiDataEnableTimerInterrupt(&device, 1);

Compatibility DLLs (older API Support)

Some users already operate software built using the former Windows KMDF driver and its associated C API, providing functions such as:

  • i_APCI1500_Init()

  • i_APCI1032_Read1DigitalInput()

  • i_APCI1564_WriteDigitalOutput()

To support these use cases, Addi-Pack provides Compatibility DLLs that expose the same function names and structures as the former API, while internally redirecting calls to the modern Addi-Pack API.

How It Works

When a program calls the old API:

i_APCI1032_Read1DigitalInput()

the Compatibility DLL transparently redirects this call to:

AddiDataReadSingleDigitalInput()

The original function names stay valid, but the execution now uses the new Addi-Pack driver.

Why This Matters

This mechanism allows users to:

  • Keep existing Windows applications running without rewriting a single line of code,

  • Move from the legacy KMDF driver to Addi-Pack safely,

  • Replace hardware (PCI → PCIe) while keeping the same software stack,

  • Migrate gradually to the new API.

How to Use Compatibility DLLs (Windows)

  1. Install Addi-Pack normally.

  2. The Compatibility DLLs are installed in system32

  3. Run the program normally — no source code changes required.

Note

For all new development, the native Addi-Pack C, C++, or Python API is recommended for improved maintainability, performance, and cross-platform behaviour. A program written for APCI-1032 cannot run on APCIe-1032 simply by using the DLL. The Compatibility DLL only ensures that existing code keeps working with the same model it was written for, but under Addi-Pack.

Use PCI Samples (Old Windows KMDF) on PCIe Hardware (Windows) using PCI DLL

This feature lets the users keep their code unchanged when switching from APCI-XXXX to APCIE-XXXX. With this feature, the users can allow PCIe to be used with PCI functions and their DLL. By default, this feature is disabled (Windows 32/64 Bits version). To enable it, the users have to manually configure a Windows Register.

To enable the feature, the users can follow these steps :

  • Install Addi-Pack as described in the section Installation Guide

  • Open Run console by using shortcut WIN+R and type regedit (admin rights are needed).

  • Once regedit opened, edit the register named Compatibility DLL UsePCIeAsPCI. Depending the architectures, the register will be differently located.
    • Windows 32Bits version : The register to update is located under the path : HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Addi-Data GmbH\AddiPack\CompatibilityDLL UsePCIeAsPCI

    • Windows 64Bits version : The register to update is located under the path : HKEY_LOCAL_MACHINE\SOFTWARE\Addi-Data GmbH\AddiPack\CompatibilityDLL UsePCIeAsPCI

  • To enable the feature, the register must be set to 1. Otherwise, the register is set to 0 by default, which disables the feature.

Fundamentals

Timer

A timer generates periodic events based on a configured timebase (µs/ms/s) and reload value. When the timer expires, it reloads and optionally triggers an interrupt.

In Addi-Pack, a timer is configured by:

  • selecting a timebase,

  • choosing a reload value,

  • applying optional modes (single-cycle or continuous),

  • then starting the timer.

Timers are ideal for periodic sampling, recurring control loops, or triggering regular actions (e.g., every 1 ms, 10 ms, or 100 ms).

Counter

A counter increments based on external pulses coming from a digital input. It is used to measure frequency, count events, or track edge transitions from sensors, encoders, or pulse sources.

In Addi-Pack, a counter is configured by:

  • selecting the counter index,

  • setting a reload value,

  • setting options if needed like edge type (rising, falling, or both) or optional gate/trigger modes (depending on the card),

  • then starting the counter.

Each time the counter reaches its reload value, it can optionally raise an interrupt. The current value and status can be read at any time.

Counters are ideal for pulse counting, speed or frequency measurement, encoder signals, or any application that needs precise tracking of external events.

Watchdog

A watchdog is a safety timer that must be refreshed (“kicked”) before its configured timeout expires. If the application fails to refresh it in time, the watchdog triggers an interrupt to signal a stall, deadlock, or timing violation.

In Addi-Pack, a watchdog is configured by:

  • selecting the watchdog index,

  • choosing a timebase (µs/ms/s),

  • setting a reload value (timeout),

  • applying optional modes depending on the board,

  • then starting the watchdog.

Watchdogs are ideal for detecting stalled loops, monitoring task deadlines, and ensuring that critical cyclic processes continue to run correctly.

Next sections:

  • Head over to Addi-Pack Samples to try interactive examples for timers, counters, and digital I/O.