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 |
C++ |
Modern object-oriented API under the |
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:
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 |
|---|---|
|
Success – Operation completed successfully ( |
|
API / TIMER / INIT / INVALID_PARAMETER – Invalid argument passed to |
|
API / DIGIO / WRITE_DIGOUT / NOT_SUPPORTED – Digital output operation not supported on this device. |
|
Driver / WATCHDOG / START / INVALID_STATE – Watchdog cannot be started (invalid or inactive state). |
|
API / INTERRUPTS / CREATE_INTERRUPT / HARDWARE_FAILURE – Hardware refused interrupt creation. |
|
API / COUNTER / READ_STATUS / OUT_OF_BOUNDS – Counter index outside the valid range. |
|
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).
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
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
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:
Select the input port to monitor (e.g., Port 0 or 1),
Define the bitmask of input lines to watch,
Choose the event type (rising, falling, any edge, on zero (state off), on one (state on)),
Select the logic mode (trigger if any line matches with logic OR, or all with logic AND),
Enable digital and device-level interrupts.
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 (
ORorAND) 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 |
|---|---|
|
Defines timer units (µs, ms, s) |
|
Timer/Counter/Watchdog initialization options |
|
Rising/Falling edge, Level High/Low |
|
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.