A modern C++ and C API for NI DAQ hardware abstraction
This document explains how to use the cppDaq library with two distinct interfaces:
| Interface | Description | Use Case |
|---|---|---|
C++ API (cppDaq) |
✅ Recommended interface with strong type safety | Modern C++ projects |
C API (daq_c.h) |
C code, FFI, or legacy integration |
The cppDaq library provides a comprehensive NI DAQ abstraction with:
- 📊 Analog I/O: Read and write analog signals
- 🔌 Digital I/O: Control digital pins with HIGH/LOW states
- 📡 PWM Generation:
- Hardware-based digital PWM (high precision)
- Software-emulated digital PWM (flexible)
- Analog PWM generation
- 🔍 Device Management: List and select available DAQ devices
- ⚡ Status Monitoring: Real-time error checking and validation
- CMake ≥ 3.20
- C++23 compatible compiler
- NI-DAQmx drivers installed
# 1. Create build directory
mkdir build && cd build
# 2. Configure CMake
cmake ..
# 3. Build the library
make- Windows:
cppDaq.dll - Linux:
libcppDaq.so
💡 Note: The C++ API uses strong types (
analog_pin,digital_pin,Frequency_hz<double>, etc.) to prevent manipulation errors at compile-time.
Before using the C++ API, ensure you have:
- ✅ Included the
cppDaqheader - ✅ Linked the
cppDaqlibrary and NI-DAQmx dependencies - ✅ Verified device availability using
GetDeviceList()
#include "cppDaq"
#include <iostream>
int main()
{
// 1. Detect available devices
auto devices = GetDeviceList();
if (devices.empty()) {
std::cerr << "❌ No device detected\n";
return 1;
}
// 2. Configure the DAQ with fluent API
DaqConfig config;
config.withDevice(devices[0])
.withAnalogPins({0})
.withDigitalPins({0})
.withAnalogContinuous({})
.withDigitalContinuous({0});
// 3. Initialize the DAQ
cppDaq daq(config);
// 4. Verify initialization
if (daq.GetStatus() != DAQ_STATUS::NO_ERR) {
std::cerr << "❌ DAQ initialization error\n";
return 1;
}
// 5. Read analog input
const double voltage = daq.ReadAnalogPin(analog_pin(0));
std::cout << "📊 AI0 = " << voltage << " V\n";
// 6. Write digital output
daq.WriteDigitalPin(digital_pin(0), digital_state::HIGH);
std::cout << "✅ Digital pin 0 set to HIGH\n";
return 0;
}| Type | Description |
|---|---|
analog_pin |
Strong type for analog pin identifiers |
digital_pin |
Strong type for digital pin identifiers |
analog_pin_continuous |
Analog pin for continuous/streaming operations |
digital_pin_continuous |
Digital pin for continuous/streaming operations |
analog_pins |
Collection of analog pins for configuration |
digital_pins |
Collection of digital pins for configuration |
Frequency_hz<double> |
Type-safe frequency representation in Hertz |
digital_state::{LOW, HIGH} |
Enumeration for digital logic states |
std::vector<std::string> GetDeviceList()Retrieves all detected DAQ device names.
| Input | None |
| Output | List of device names (e.g., "Dev1", "Dev2") |
| Usage | Choose the device to pass to DaqConfig::withDevice() |
Example:
auto devices = GetDeviceList();
for (const auto& dev : devices) {
std::cout << "Found device: " << dev << "\n";
}cppDaq::cppDaq(const DaqConfig& config)Constructs a DAQ instance using the modern configuration API.
| Input | DaqConfig object (device + pin configurations) |
| Output | cppDaq instance |
| Note | GetStatus() after construction |
Example:
DaqConfig config;
config.withDevice("Dev1")
.withAnalogPins({0, 1})
.withDigitalPins({0, 1, 2});
cppDaq daq(config);
if (daq.GetStatus() != DAQ_STATUS::NO_ERR) {
// Handle error
}cppDaq::cppDaq(const std::string, const analog_pins,
const digital_pins, const analog_pins_continuous,
const digital_pins_continuous)| Status | |
| Recommendation | Use DaqConfig constructor instead |
DAQ_STATUS cppDaq::GetStatus()Returns the current operational state of the DAQ.
Possible Return Values:
| Status | Meaning |
|---|---|
NO_ERR |
✅ Everything operational |
INPUTS_NOT_VALID |
❌ Invalid input parameters |
DAQ_RETRIEVE_NAME_FAIL |
❌ Failed to retrieve device name |
DAQ_TEST_DEVICE_FAIL |
❌ Device test failed |
DAQ_NAME_EMPTY |
❌ Device name is empty |
ERR_CREATE_IO_FAIL |
❌ I/O creation failed |
DAQ_NOT_INITIALIZED |
❌ DAQ not properly initialized |
double cppDaq::ReadAnalogPin(analog_pin pin)Reads the voltage from a single analog input.
| Input | analog_pin - Pin identifier |
| Output | Voltage in volts (double) |
| Precondition | Pin must be configured in analog pins |
Example:
double voltage = daq.ReadAnalogPin(analog_pin(0));std::array<double, 1000> cppDaq::ReadMultipleAnalogPin(analog_pin pin)Acquires 1000 samples from an analog input.
| Input | analog_pin - Pin identifier |
| Output | Array of 1000 voltage samples |
| Usage | Burst acquisition or waveform capture |
Example:
auto samples = daq.ReadMultipleAnalogPin(analog_pin(0));
double average = std::accumulate(samples.begin(), samples.end(), 0.0) / samples.size();bool cppDaq::WriteAnalogPin(analog_pin pin, double value)Writes a voltage to an analog output.
| Input | analog_pin - Pin identifierdouble - Voltage value |
| Output | true if successful, false otherwise |
Example:
if (daq.WriteAnalogPin(analog_pin(0), 3.3)) {
std::cout << "✅ Voltage set to 3.3V\n";
}bool cppDaq::WriteDigitalPin(digital_pin pin, digital_state state)Sets a digital pin to HIGH or LOW.
| Input | digital_pin - Pin identifierdigital_state - LOW or HIGH |
| Output | true if successful, false otherwise |
Example:
daq.WriteDigitalPin(digital_pin(0), digital_state::HIGH);
daq.WriteDigitalPin(digital_pin(1), digital_state::LOW);bool cppDaq::StartDigitalCounterPWM(digital_pin_continuous pin,
Frequency_hz<double> rate)Starts hardware-based digital PWM using a counter.
| Input | digital_pin_continuous - Pin identifierFrequency_hz<double> - Frequency |
| Output | true if started successfully |
| Advantage | ⚡ High precision, hardware-timed |
Example:
daq.StartDigitalCounterPWM(digital_pin_continuous(0), Frequency_hz<double>(1000.0));bool cppDaq::StopDigitalCounterPWM()Stops the active hardware PWM.
| Input | None |
| Output | true if stopped successfully |
bool cppDaq::StartDigitalSoftwarePWM(digital_pin_continuous pin,
Frequency_hz<double> rate)Starts software-emulated digital PWM.
| Input | digital_pin_continuous - Pin identifierFrequency_hz<double> - Frequency |
| Output | true if started successfully |
| Note |
bool cppDaq::StopDigitalSoftwarePWM()Stops the active software PWM.
bool cppDaq::UpdateDigitalSoftwarePWM(Frequency_hz<double> rate)Updates the frequency of a running software PWM.
| Input | New frequency |
| Output | true if updated successfully |
| Note | Must be called while PWM is active |
bool cppDaq::StartAnalogPWM(analog_pin_continuous pin,
Frequency_hz<double> rate)Starts periodic analog signal generation.
| Input | analog_pin_continuous - Pin identifierFrequency_hz<double> - Frequency |
| Output | true if started successfully |
bool cppDaq::StopAnalogPWM(analog_pin_continuous pin)Stops the periodic analog generation on the specified pin.
| Input | analog_pin_continuous - Pin identifier |
| Output | true if stopped successfully |
cppDaq::~cppDaq()Destructor that automatically releases all resources.
| Resources | Tasks, handles, memory allocations |
| Note | Automatically called when object goes out of scope |
The C interface provides ABI compatibility but has limitations:
| Limitation | Impact |
|---|---|
| ❌ No strong types | Less compile-time safety |
| ❌ Limited validation | Runtime errors more likely |
| Risk of leaks if not careful |
Recommended for: C projects, FFI (Foreign Function Interface), or legacy system integration.
#include "daq_c.h"
#include <stdio.h>
int main(void)
{
// 1. Configure pins
char dev[] = "Dev1";
int digital_pins[] = {0};
// 2. Create DAQ instance
HDAQ h = create_cppDaq_(
dev,
0, NULL, // No analog pins
1, digital_pins, // 1 digital pin (pin 0)
0, NULL, // No continuous analog
0, NULL // No continuous digital
);
// 3. Check creation
if (h == NULL) {
printf("❌ Error: create_cppDaq_ failed\n");
return 1;
}
// 4. Set digital pin HIGH
if (!set_digital_value_(h, 0, 1)) {
printf("❌ Error: set_digital_value_ failed\n");
} else {
printf("✅ Digital pin 0 set to HIGH\n");
}
// 5. Cleanup
destroy_cppDaq_(h);
return 0;
}Note: All C functions use an opaque handle
HDAQ(which is avoid*pointer).
HDAQ create_cppDaq_(
char* device,
int analog_count,
int* analog_io_id,
int digital_count,
int* digital_io_id,
int analog_co_count,
int* analog_co_io_id,
int digital_co_count,
int* digital_co_io_id
)Creates a DAQ instance and returns an opaque handle.
| Parameter | Description |
|---|---|
device |
Device name (e.g., "Dev1") |
analog_count |
Number of analog pins |
analog_io_id |
Array of analog pin IDs (or NULL if count = 0) |
digital_count |
Number of digital pins |
digital_io_id |
Array of digital pin IDs (or NULL if count = 0) |
analog_co_count |
Number of continuous analog pins |
analog_co_io_id |
Array of continuous analog pin IDs |
digital_co_count |
Number of continuous digital pins |
digital_co_io_id |
Array of continuous digital pin IDs |
| Returns | Valid HDAQ handle or NULL on failure |
Example:
int analog_pins[] = {0, 1};
int digital_pins[] = {0};
HDAQ h = create_cppDaq_(
"Dev1",
2, analog_pins, // 2 analog pins
1, digital_pins, // 1 digital pin
0, NULL, // No continuous analog
0, NULL // No continuous digital
);bool destroy_cppDaq_(HDAQ handle)Destroys the DAQ instance and releases all resources.
| Input | HDAQ handle |
| Returns | true if successful |
| Note |
int get_status_(HDAQ handle)Returns the current status code of the DAQ instance.
Status Code Mapping:
| Code | Status Constant | Meaning |
|---|---|---|
0 |
NO_ERR |
✅ Normal operation |
1 |
INPUTS_NOT_VALID |
❌ Invalid input parameters |
2 |
DAQ_RETRIEVE_NAME_FAIL |
❌ Failed to retrieve device name |
3 |
DAQ_TEST_DEVICE_FAIL |
❌ Device test failed |
4 |
DAQ_NAME_EMPTY |
❌ Empty device name |
5 |
ERR_CREATE_IO_FAIL |
❌ I/O creation failed |
6 |
DAQ_NOT_INITIALIZED |
❌ DAQ not initialized |
Example:
int status = get_status_(h);
if (status != 0) {
printf("DAQ error: %d\n", status);
}double get_analog_value_(HDAQ daq, int pin)Reads the voltage from an analog input pin.
| Input | HDAQ handle, pin index |
| Returns | Voltage in volts (double) |
Example:
double voltage = get_analog_value_(h, 0);
printf("Analog pin 0: %.2f V\n", voltage);bool set_analog_value_(HDAQ handle, int pin, double value)Writes a voltage to an analog output pin.
| Input | HDAQ handle, pin index, voltage value |
| Returns | true if successful |
Example:
if (set_analog_value_(h, 0, 3.3)) {
printf("✅ Set analog pin 0 to 3.3V\n");
}bool set_digital_value_(HDAQ handle, int pin, int state)Writes a digital state to a pin.
| Input | HDAQ handle, pin index, state (0 = LOW, 1 = HIGH) |
| Returns | true if successful |
Example:
set_digital_value_(h, 0, 1); // Set HIGH
set_digital_value_(h, 1, 0); // Set LOWbool start_digital_pulse_(HDAQ handle, int pin, int frequency)Starts hardware-based digital PWM.
| Input | HDAQ handle, pin index, frequency in Hz |
| Returns | true if started successfully |
Example:
start_digital_pulse_(h, 0, 1000); // 1 kHz PWMbool stop_digital_pulse_(HDAQ handle)Stops the hardware digital PWM.
bool start_digital_software_pulse_(HDAQ handle, int pin, int frequency)Starts software-emulated digital PWM.
| Note |
bool stop_digital_software_pulse_(HDAQ handle)Stops the software digital PWM.
bool update_digital_software_pulse_(HDAQ handle, int frequency)Updates the frequency of an active software PWM.
| Input | HDAQ handle, new frequency in Hz |
| Returns | true if successful |
bool start_analog_pulse_(HDAQ handle, int pin, int frequency)Starts periodic analog signal generation.
| Input | HDAQ handle, pin index, frequency in Hz |
| Returns | true if successful |
bool stop_analog_pulse_(HDAQ handle, int pin)Stops the periodic analog signal on the specified pin.
| Input | HDAQ handle, pin index |
| Returns | true if successful |
| Practice | Reason |
|---|---|
| ✅ Prefer C++ API | Type safety prevents common errors at compile-time |
| ✅ Check status after creation | Use GetStatus() or get_status_() immediately |
| ✅ Verify return values | All write/PWM operations return bool - check them! |
| ✅ Stop PWMs before cleanup | Explicitly stop all PWM signals before destroying DAQ |
✅ Use DaqConfig |
Modern fluent API is clearer than old constructor |
// ✅ Good: Always check status
cppDaq daq(config);
if (daq.GetStatus() != DAQ_STATUS::NO_ERR) {
std::cerr << "Initialization failed\n";
return;
}
// ✅ Good: Verify write operations
if (!daq.WriteDigitalPin(digital_pin(0), digital_state::HIGH)) {
std::cerr << "Failed to write digital pin\n";
}// ✅ Good: Always cleanup
HDAQ h = create_cppDaq_(...);
if (h == NULL) {
return 1;
}
// ... use DAQ ...
// Stop PWM before destroying
stop_digital_pulse_(h);
// Always destroy
destroy_cppDaq_(h);// ✅ Good: Stop before switching modes
daq.StopDigitalCounterPWM();
daq.StartDigitalSoftwarePWM(digital_pin_continuous(0), Frequency_hz<double>(500.0));
// ✅ Good: Stop before destruction
daq.StopDigitalSoftwarePWM();
// Destructor will run automaticallyFor issues, questions, or contributions, please refer to the project repository.
Made with ❤️ for NI DAQ automation