API Programming Guide

Overview

The Addi-Pack API provides a unified programming interface for all supported ADDI-DATA devices. It offers high-level access to hardware functions through a consistent set of C, C++, and Python APIs.

You can use the API to:

  • Enumerate and select connected cards

  • Read or write digital and analog I/O

  • Configure and control timers, counters, and watchdogs

  • Manage interrupts and handle callbacks

  • Monitor status, errors, and card information

Tip

The same API calls work seamlessly across Windows, Linux, and all ADDI-DATA devices (PCI, PCIe and CPCI/CPCIs).

Language Interfaces

Addi-Pack provides equivalent API’s for three languages:

Language

Description

C

Low-level procedural API via addi-data/addi-pack/addi-pack.h

C++

Modern object-oriented API under the addipack:: namespace

Python

High-level bindings generated from the C++ API

All bindings follow the same logical structure and naming convention:

  • C: AddiData<Action><Component>()

  • C++: component->actionComponent()

  • Python: component.action_component()

Example mappings:

C

C++

Python

AddiDataInitTimer()

timer->initTimer()

timer.init_timer()

AddiDataReadSingleDigitalInput()

digital->readSingleDigitalInput()

digital.read_single_digital_input()

AddiDataStartCounter()

counter->startCounter()

counter.start_counter()

Typical Workflow

The general API workflow is the same across all languages:

digraph addipack_workflow { rankdir=TB; node [shape=box, style="rounded,filled", color=black, fillcolor=white, fontname="Arial", fontsize=10]; // Define discover and note on same rank { rank = same; discover; cnote; } // Main flow discover [label="Discover a card\n\nC: AddiDataGetDeviceList()\nC++: DeviceDiscovery::getDevices()\nPy: DeviceDiscovery.get_devices()\n\nRetrieve the first available device from the system"]; init [label="Initialise a component\n\nC: AddiDataInitTimer()\nC++: timer->initTimer()\nPy: timer.init_timer()\n\nInitialise the timer with the given parameters"]; start [label="Start or trigger\n\nC: AddiDataStartTimer()\nC++: timer->startTimer()\nPy: timer.start_timer()\n\nStart the timer"]; read [label="Read results or handle event\n\nC: AddiDataReadTimerStatus()\nC++: timer->readTimerStatus()\nPy: timer.read_timer_status()\n\nRead the current status of the timer or respond to an interrupt event"]; stop [label="Stop or release\n\nC: AddiDataStopTimer()\nC++: timer->stopTimer()\nPy: timer.stop_timer()\n\nStop the timer"]; // C-specific note cnote [shape=note, color=gray30, fontcolor=gray30, label="C only:\nBefore calling AddiDataGetDeviceList():\n- Call AddiDataGetNumberOfDevices()\n- Allocate memory using malloc()"]; // Connections discover -> init -> start -> read -> stop; discover -> cnote [style=dotted, arrowhead=none]; }

Typical Addi-Pack API workflow (Timer example)

Examples

C

#include <addi-data/addi-pack/addi-pack.h>
#include <stdio.h>

int main() {
   uint8_t number_of_cards = 0;
   AddiDataGetNumberOfDevices(&number_of_cards);

   AddiDataDeviceStruct* device_list = malloc(sizeof(AddiDataDeviceStruct) * number_of_cards);
   if (!device_list || AddiDataGetDeviceList(device_list) != ADDIDATA_SUCCESS_CODE) {
      free(device_list);
      exit(EXIT_FAILURE);
 }

   uint8_t timer_id = 0;
   uint16_t reload_value = 500;
   TIMEBASE_UNITS timebase = MILLISECOND;
   uint32_t options = SINGLE_CYCLE;

   AddiDataInitTimer(&device, timer_id, timebase, reload_value, options);
   AddiDataStartTimer(&device, timer_id);

   uint32_t value = 0;
   uint8_t status = 0, triggered = 0, has_expired;

   AddiDataReadTimerStatus(id, timer_id, &value, &status, &triggered);

   for (int i = 0, i < reload_value; i++) {
      printf("Timer: value=%u | status=%u | triggered=%u | expired=%u\r", value, status, triggered, has_expired);
      fflush(stdout);
      usleep(1000);
   }

   AddiDataStopTimer(id, timer_id);
   return 0;
}

C++

#include <addipack/device_discovery.hpp>
#include <addipack/timer_interface.hpp>
#include <chrono>
#include <thread>

int main() {
    auto devices = DeviceDiscovery::getDevices();
    auto timer = device->timer();

    uint8_t timer_id = 0;
    uint16_t reload_value = 500;
    TIMEBASE_UNITS timebase = MILLISECOND;
    uint32_t options = SINGLE_CYCLE;

    timer->initTimer(timer_id, timebase, reload_value, options);
    timer->startTimer(timer_id);

    uint32_t value = 0;
    uint8_t status = 0, triggered = 0, has_expired;

    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    timer->readTimerStatus(timer_id, value, status, triggered, has_expired);

    std::cout << "Timer: value=" << value << " | status=" << int(status)
               << " | triggered=" << int(triggered) << " | expired=" << int(has_expired) << std::endl;

    timer->stopTimer(timer_id);
    return 0;
}

Python

import time
from addipack import (
   DeviceDiscovery,
   TIMEBASE_UNITS
)

devices = DeviceDiscovery.get_devices()
timer = device.timer()

timer_id = 0
reload_value = 500
timebase = TIMEBASE_UNITS.MILLISECOND
options = ADDI_DATA_TCW_OPTIONS.SINGLE_CYCLE

timer.init_timer(timer_id, timebase, reload_value, options)
timer.start_timer(timer_id)
print("Timer started.")

time.sleep(0.1)
value, status, triggered, has_expired = timer.read_timer_status(timer_id)
print(f"Timer {timer_id} - value: {value}, started: {status}, trigger: {triggered}, expired: {has_expired}")

timer.stop_timer(timer_id)

Error Handling

All Addi-Pack C functions return a 32-bit status code that encodes where and why an error occurred.

Error Code Layout

The error code is divided into 5 main fields:

Bits

Field

Size

Description

31

Source

1 bit

0 = Driver, 1 = API

30–24

Domain

7 bits

Functional area (Timer, Counter, Digital I/O…)

23–16

Reserved

8 bits

Reserved for future use

15–8

Function

8 bits

Function identifier (Init, Start, Stop…)

7–0

Reason

8 bits

Detailed cause (Invalid, Out of range…)

Example

Return codes are universal — they are interpreted the same way in C, C++, and Python (where exceptions include the code).

Error Code (Hex)

Meaning

0x00000000

Success – Operation completed successfully (ADDIDATA_SUCCESS_CODE).

0x83001203

API / TIMER / INIT / INVALID_PARAMETER – Invalid argument passed to AddiDataInitTimer().

0x82001905

API / DIGIO / WRITE_DIGOUT / NOT_SUPPORTED – Digital output operation not supported on this device.

0x04001309

Driver / WATCHDOG / START / INVALID_STATE – Watchdog cannot be started (invalid or inactive state).

0x85000608

API / INTERRUPTS / CREATE_INTERRUPT / HARDWARE_FAILURE – Hardware refused interrupt creation.

0x81001804

API / COUNTER / READ_STATUS / OUT_OF_BOUNDS – Counter index outside the valid range.

0x8000000A

API / GENERIC / NONE / CONFIG_ERROR – Configuration issue detected in API call.

Note

This list shows only a few representative examples. The complete list of all error codes is available in the Common Error Codes.

Working with Structures and Types

Structure AddiDataDeviceStruct

Describes a detected ADDI-DATA device and its capabilities.

This structure is returned by discovery functions such as AddiDataGetDeviceList(). It contains hardware information like the product name, PCI/PCIe location, and supported features (timers, counters, digital I/O, etc.).

Only the id field (a const char*) is used in API calls. This string uniquely identifies the device and must be passed to all AddiData*() functions.

typedef struct AddiDataDeviceStruct {
    char id[ADDI_STRING_SIZE];
    AddiDataGeneralInformations general;
    AddiDataFunctionInformations functions;
    AddiDataPCIBusInformations bus;
} AddiDataDeviceStruct;

You obtain it through:

AddiDataDeviceStruct device;
device->general.product_name

Example:
printf("\n========== Device(%s) ==========\n", device->id);
printf("Product Name: %s\n", device->general.product_name);
printf("Bus: %u | Device: %u | Vendor ID: 0x%04X | Device ID: 0x%04X\n", device->bus.bus_number,
        device->bus.device_number, device->bus.vendor_id, device->bus.device_id);
printf("\nDigital Inputs: %u | Outputs: %u | Digital Ports: %u | Interrupt Mask: 0x%08X\n",
            device->functions.digital.number_inputs, device->functions.digital.number_outputs,
            device->functions.digital.number_of_ports, device->functions.digital.interrupt_mask);

Timer / Counter / Watchdog Identifiers

Each ADDI-DATA card exposes a set of timers, counters, and watchdogs, identified by zero-based IDs.

The number of available components depends on the specific card model, and is provided dynamically through the discovery structure.

To determine valid IDs, use the fields inside AddiDataDeviceStruct:

uint8_t max_timer_id = device.functions.timer.number_timers - 1;
uint8_t max_counter_id = device.functions.counter.number_counters - 1;
uint8_t max_watchdog_id = device.functions.watchdog.number_watchdogs - 1;

For example, if number_timers == 3, then the valid timer IDs are 0, 1, and 2.

Card

Timers ID

APCI-1500

Timers 0–2

APCIe-1564

Timers 0–1

APCIe-1032

Timer 0

APCI-1032

None

Example usage:

AddiDataStartTimer(&device, 0);  // Start timer 0

To determine how many are available:

Use the sample get_devices_info to query the detected card and list its capabilities (number of timers, counters, watchdogs, digital inputs/outputs, ports, etc.).

Run (CLI):

Linux

$AddiPack_ROOT/samples/get_device_info/build/get_device_info

Windows (PowerShell)

& "$env:AddiPack_ROOT\samples\bin\x64\generic\get_devices_info.exe"

The output includes cards summary with counts for timers/counters/watchdogs and digital I/O.

Digital I/O Channels

Digital channels are identified numerically and can be accessed either individually (bit-level) or collectively (port-level).

  1. Single-bit access

    Use this when you want to read or write a specific channel:

    uint8_t value = 0;
    AddiDataReadSingleDigitalInput(device.id, 4, &value);  // Read input channel 4
    
    AddiDataWriteSingleDigitalOutput(device.id, 7, ADDI_DATA_ENABLE);     // Set output channel 7 to HIGH
    
  2. Multi-bit (mask-based) access

    This allows reading or writing all digital inputs or outputs at once using a bitmask:

    uint32_t inputs = 0;
    AddiDataReadAllDigitalInputs(device.id, &inputs);     // Read all 32 inputs
    
    AddiDataWriteAllDigitalOutputs(device.id, 0x0000FFFF); // Set first 16 outputs to HIGH
    
  3. Digital Input Events (Interrupts)

    Some ADDI-DATA cards support event-based monitoring of digital input lines. This allows the application to react to signal changes instead of polling continuously.

    To configure digital input interrupts:

    1. Select the input port to monitor (e.g., Port 0 or 1),

    2. Define the bitmask of input lines to watch,

    3. Choose the event type (rising, falling, any edge, on zero (state off), on one (state on)),

    4. Select the logic mode (trigger if any line matches with logic OR, or all with logic AND),

    5. Enable digital and device-level interrupts.

  4. Digital Output Events (VCC / CC Interrupts)

    Digital output interrupts are triggered when the board detects:

    • VCC interrupt: power supply failure on digital outputs

    • CC interrupt: short-circuit condition on digital outputs

    These events are card-level safety mechanisms and are independent of individual output channel states.

Example (C code):

uint8_t port = 0;
uint32_t mask = 0x00000010; // Channel 4
DIGITAL_EVENT_TYPE event = RISING_EDGE;
DIGITAL_EVENT_LOGIC logic = OR;

AddiDataSetInterruptEventForInputs(device.id, mask, event);
AddiDataSetInterruptLogicForPort(device.id, port, logic);
AddiDataSetDeviceInterruptCallback(device.id, &MyCallback);
AddiDataEnableDeviceInterrupts(device.id);
AddiDataEnableDigitalInterrupt(device.id);

Note

  • Event logic is configured per port, not per channel.

  • Cards may support different event types: RISING_EDGE, FALLING_EDGE, ANY_EDGE, ON_ZERO, ON_ONE.

  • Logic mode (OR or AND) determines if the interrupt fires on any active line, or only when all specified inputs match.

Tip

You can retrieve the number of available ports and the interrupt mask using the discovery structure.

uint32_t interrupt_mask = selected_device->functions.digital.interrupt_mask;
uint8_t max_port = selected_device->functions.digital.number_of_ports;

Enumerations

Enumerations standardize parameters such as timebases, modes, and event types.

Common enumerations:

Enumeration

Purpose

TIMEBASE_UNITS

Defines timer units (µs, ms, s)

ADDI_DATA_TCW_OPTIONS

Timer/Counter/Watchdog initialization options

DIGITAL_EVENT_TYPE

Rising/Falling edge, Level High/Low

DIGITAL_EVENT_LOGIC

Logical combination (AND / OR)

Example:

TIMEBASE_UNITS timebase = MILLISECOND;
AddiDataInitTimer(&device, 0, timebase, 1000, 0);

Next section: Driver Concepts Guide — Learn how drivers, interrupts, and DMA work internally.