.. _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_PCI1032_Read1DigitalInput()``
- ``i_PCI1564_Set1DigitalOutputOn()``

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.

.. Note::
   If the code uses functions with an 'A' before PCI (e.g. i_APCI1500_SetBoardIntRoutineWin32),
   the Compatibility DLL delivered in the present ADDI-PACK software will not be enough to run the associated code.
   If it is needed to run a former code using said functions, contact ADDI-DATA. (**info@addi-data.com**)

.. Note::
   If you have former Windows KMDF drivers installed, you must uninstall them before installing Addi-Pack. This is required to use the Compatibility DLLs.

How It Works
~~~~~~~~~~~~

When a program calls the old API::

   i_PCI1032_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.
   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 -- Compatibility DLL Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This feature lets the user keep their code unchanged when switching from APCI-XXXX to APCIE-XXXX. With this feature, the user can allow PCIe to be used with PCI functions and their DLL.
By default, this feature is disabled on all Windows versions (32-bit and 64-bit). To enable it, the user has to manually configure a Windows registry key.

To enable the feature, the user 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` is open, edit the registry key named `CompatibilityDLL UsePCIeAsPCI`. The registry path depends on whether your process runs under WOW64 (32-bit process on a 64-bit Windows system).
   - **Standard path** (no WOW64 redirection) — applicable when running a 64-bit process on a 64-bit Windows system, or a 32-bit process on a 32-bit Windows system: `HKEY_LOCAL_MACHINE\\SOFTWARE\\Addi-Data GmbH\\AddiPack\\CompatibilityDLL UsePCIeAsPCI`
   - **WOW64-redirected path** —  applicable when running a 32-bit process on a 64-bit Windows system: `HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Addi-Data GmbH\\AddiPack\\CompatibilityDLL UsePCIeAsPCI`
- To enable the feature, set the registry value to **1** (DWORD). The default value is **0**, which disables the feature.

*Note on WOW64 registry redirection*: On 64-bit Windows, 32-bit processes are subject to automatic registry redirection. When a 32-bit process reads or writes to `HKEY_LOCAL_MACHINE\\SOFTWARE`, 
Windows transparently redirects the access to `HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node`. This means that if you configure the standard path (`HKLM\\SOFTWARE\\...`) using a 32-bit tool on a 64-bit system, 
Windows will automatically store the value under `WOW6432Node`. Conversely, if you use regedit (a 64-bit tool) on a 64-bit system, you must manually navigate to WOW6432Node to edit the key for a 32-bit application.


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.
