.. _driver_concepts:

=====================
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.

.. raw:: html

   <div class="page-break"></div>

C Example:

.. code-block:: c

   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;
   }

.. code-block:: c

   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:**

.. code-block:: bash

   $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?

.. code-block:: c

   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 :ref:`installation`
- 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 :ref:`samples` to try interactive examples for timers, counters, and digital I/O.
.. - For troubleshooting driver-level issues, visit the :ref:`maintenance` section.
