MRPT  1.9.9
CNationalInstrumentsDAQ.h
Go to the documentation of this file.
1 /* +------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | https://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2020, Individual contributors, see AUTHORS file |
6  | See: https://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See: https://www.mrpt.org/License |
8  +------------------------------------------------------------------------+ */
9 
10 #pragma once
11 
13 #include <mrpt/io/CPipe.h>
16 
17 #include <atomic>
18 #include <list>
19 #include <memory>
20 #include <thread>
21 
22 namespace mrpt::hwdrivers
23 {
24 /** An interface to read from data acquisition boards compatible with National
25  * Instruments "DAQmx Base" or "DAQmx".
26  * Refer to DAQmx Base C API reference online to learn more on the concepts of
27  * "channels", "tasks" (which in this MRPT class
28  * are mapped to independent grabbing threads), etc.
29  * If both DAQmx and DAQmxBase are installed in the system, DAQmx will be used.
30  * This class API isolate the user from the usage of one or another specific
31  * library.
32  *
33  * This class can be used as a sensor from the application "rawlog-grabber", or
34  * directly as a C++ class from a user program.
35  * Refer to the example: [MRPT]/samples/NIDAQ_test
36  *
37  * Samples will be returned inside mrpt::obs::CObservationRawDAQ in "packets"
38  * of a predefined number of samples, which can be changed by the user through
39  * the "samplesPerChannelToRead" parameter of each task.
40  *
41  * For multichannels tasks, samples will be **interleaved**. For example, the
42  * readings from successive timesteps for 4 ADC channels
43  * will be available in the ADC vector inside mrpt::obs::CObservationRawDAQ in
44  * this order:
45  *
46  * - A0[0] A1[0] A2[0] A3[0] A0[1] A1[1] A2[1] A3[1] A0[2] A1[2] A2[2] A3[2]
47  * ...
48  *
49  * The sensor label (field "m_sensorLabel") of each grabbed observation will be
50  * the concatenation of this class sensor label,
51  * a dot (".") and the task label (default="task###", with ### the task index).
52  *
53  * \code
54  * PARAMETERS IN THE ".INI"-LIKE CONFIGURATION STRINGS:
55  * -------------------------------------------------------
56  * [supplied_section_name]
57  * ; Number of tasks (each will run in a thread). Task indices are 0-based.
58  * ; (Parameters below follow NIs DAQmx API notation)
59  * num_tasks = 1
60  *
61  * ; Channels, separated by commas if more than one.
62  * ; - "ai": Analog inputs
63  * ; - "ao": Analog outputs
64  * ; - "di": Digital inputs
65  * ; - "do": Digital inputs
66  * ; - "ci_period",
67  * ; "ci_count_edges", "ci_pulse_width",
68  * ; "ci_lin_encoder", "ci_ang_encoder" : Counters & encoders (WARNING: NI
69  * says "a task can include only one counter input channel")
70  * ; - "co_pulses": Output digital pulses (WARNING: NI says "a task can include
71  * only one counter output channel")
72  * ;
73  * task0.channels = ai //, ao, di, do, ci_ang_encoder
74  * ;task0.taskLabel= MY_LABEL // Optional textual label to build the
75  * mrpt::obs::CObservation sensor label (default: task number)
76  * task0.samplesPerSecond = 1000 // Samples per second. Continuous (infinite)
77  * sampling is assumed.
78  * task0.samplesPerChannelToRead = 1000 // The number of samples to grab at
79  * once from each channel. ;task0.bufferSamplesPerChannel = 200000 // Increase
80  * if you have errors about " Onboard device memory overflow.(...)"
81  *
82  * ; Analog input channel params.
83  * task0.ai.physicalChannel = Dev1/ai0:3, Dev1/ai6
84  * task0.ai.physicalChannelCount = 5 // *IMPORTANT* This must be the total
85  * number of channels listed in "physicalChannel" (e.g. 4 for "Dev1/ai0:3")
86  * task0.ai.terminalConfig = DAQmx_Val_Cfg_Default | DAQmx_Val_RSE |
87  * DAQmx_Val_NRSE | DAQmx_Val_Diff // One of these strings
88  * task0.ai.minVal = -10.0 // Volts
89  * task0.ai.maxVal = 10.0 // Volts
90  *
91  * ; Analog output channel params.
92  * task0.ao.physicalChannel = Dev1/ao0, Dev1/ao2:4
93  * task0.ao.physicalChannelCount = 4 // *IMPORTANT* This must be the total
94  * number of channels listed in "physicalChannel" (e.g. 1 for "Dev1/ao0")
95  * task0.ao.minVal = -10.0 // Volts
96  * task0.ao.maxVal = 10.0 // Volts
97  *
98  * ; Digital input channel params.
99  * task0.di.line = Dev1/port1/line0
100  *
101  * ; Digital input channel params.
102  * task0.do.line = Dev1/port1/line2
103  *
104  * ; Counter: period of a digital signal
105  * task0.ci_period.counter = Dev1/ctr0
106  * task0.ci_period.minVal = 0 // The minimum value, in units, that you
107  * expect to measure. task0.ci_period.maxVal = 0 // The minimum value, in
108  * units, that you expect to measure. task0.ci_period.units =
109  * DAQmx_Val_Seconds | DAQmx_Val_Ticks // One of these strings
110  * task0.ci_period.edge = DAQmx_Val_Rising | DAQmx_Val_Falling // One of
111  * these strings
112  * task0.ci_period.measTime = 0 // NI says: "Always pass 0 for this
113  * parameter." task0.ci_period.divisor = 1 // NI says: "Always pass 1 for
114  * this parameter."
115  *
116  * ; Counter: count the number of rising or falling edges of a digital signal
117  * task0.ci_count_edges.counter = Dev1/ctr0
118  * task0.ci_count_edges.edge = DAQmx_Val_Rising | DAQmx_Val_Falling //
119  * One of these strings
120  * task0.ci_count_edges.initialCount = 0 // The value from which to start
121  * counting
122  * task0.ci_count_edges.countDirection = DAQmx_Val_CountUp | DAQmx_Val_CountDown
123  * | DAQmx_Val_ExtControlled // One of these strings
124  *
125  * ; Counter: measure the width of a digital pulse
126  * task0.ci_pulse_width.counter = Dev1/ctr0
127  * task0.ci_pulse_width.minVal = 0 // The minimum value, in units, that
128  * you expect to measure.
129  * task0.ci_pulse_width.maxVal = 0 // The minimum value, in units, that
130  * you expect to measure.
131  * task0.ci_pulse_width.units = DAQmx_Val_Seconds | DAQmx_Val_Ticks //
132  * One of these strings
133  * task0.ci_pulse_width.startingEdge = DAQmx_Val_Rising | DAQmx_Val_Falling //
134  * One of these strings
135  *
136  * ; Counter: uses a linear encoder to measure linear position
137  * task0.ci_lin_encoder.counter = Dev1/ctr0
138  * task0.ci_lin_encoder.decodingType = DAQmx_Val_X1 | DAQmx_Val_X2 |
139  * DAQmx_Val_X4 | DAQmx_Val_TwoPulseCounting // One of these strings
140  * task0.ci_lin_encoder.ZidxEnable = false | true | 0 | 1 // enable z
141  * indexing?
142  * task0.ci_lin_encoder.ZidxVal = 0 // The value, in units, to which to
143  * reset the measurement when signal Z is high and signal A and signal B are at
144  * the states you specify with ZidxPhase.
145  * task0.ci_lin_encoder.ZidxPhase = DAQmx_Val_AHighBHigh |
146  * DAQmx_Val_AHighBLow | DAQmx_Val_ALowBHigh | DAQmx_Val_ALowBLow // One of
147  * these strings task0.ci_lin_encoder.units = DAQmx_Val_Meters |
148  * DAQmx_Val_Inches | DAQmx_Val_Ticks // One of these strings
149  * task0.ci_lin_encoder.distPerPulse = 0.1 // The distance measured for each
150  * pulse the encoder generates. Specify this value in units.
151  * task0.ci_lin_encoder.initialPos = 0.0 // The position of the encoder when
152  * the measurement begins. This value is in units.
153  *
154  * ; Counter: uses an angular encoder to measure angular position
155  * task0.ci_ang_encoder.counter = Dev1/ctr0
156  * task0.ci_ang_encoder.decodingType = DAQmx_Val_X1 | DAQmx_Val_X2 |
157  * DAQmx_Val_X4 | DAQmx_Val_TwoPulseCounting // One of these strings
158  * task0.ci_ang_encoder.ZidxEnable = 0 | 1 | false | true // enable z
159  * indexing
160  * task0.ci_ang_encoder.ZidxVal = 0 // The value, in units, to which to
161  * reset the measurement when signal Z is high and signal A and signal B are at
162  * the states you specify with ZidxPhase.
163  * task0.ci_ang_encoder.ZidxPhase = DAQmx_Val_AHighBHigh |
164  * DAQmx_Val_AHighBLow | DAQmx_Val_ALowBHigh | DAQmx_Val_ALowBLow // One of
165  * these strings task0.ci_ang_encoder.units = DAQmx_Val_Degrees |
166  * DAQmx_Val_Radians | DAQmx_Val_Ticks // One of these strings
167  * task0.ci_ang_encoder.pulsesPerRev = 512 // The number of pulses the encoder
168  * generates per revolution.
169  * task0.ci_ang_encoder.initialAngle = 0.0 // The position of the encoder when
170  * the measurement begins. This value is in units.
171  * task0.ci_ang_encoder.decimate = 1 // Grab 1 out of N readings
172  *
173  * ; Output digital pulses:
174  * task0.co_pulses.counter = Dev1/ctr1
175  * task0.co_pulses.idleState = DAQmx_Val_High | DAQmx_Val_Low
176  * task0.co_pulses.initialDelay = 0 // The amount of time in seconds to
177  * wait before generating the first pulse.
178  * task0.co_pulses.freq = 100 // The frequency of the pulses to
179  * generate (Hertz)
180  * task0.co_pulses.dutyCycle = 0.5 // The width of the pulse divided by
181  * the pulse period.
182  * \endcode
183  *
184  * See also:
185  * - [MRPT]/samples/NIDAQ_test
186  * - Sample .ini files for rawlog-grabber in
187  * [MRPT]/share/mrpt/config_files/rawlog-grabber/
188  * - NI DAQmx C reference:
189  * http://others-help.mrpt.org/ni-daqmx_c_reference_help/
190  * - NI DAQmx Base 3.x C reference:
191  * http://others-help.mrpt.org/ni-daqmx_base_3.x_c_function_reference/
192  *
193  * DAQmx Base Installation
194  * ------------------------
195  * Go to http://ni.com and download the "DAQmx Base" package for your OS.
196  * Install following NI's instructions. As of 2013, the latest version is 3.7.
197  *
198  * \note This class requires compiling MRPT with support for "NI DAQmx" or "NI
199  * DAQmx Base". While compiling MRPT,
200  * check the "MRPT_HAS_NI_DAQmx"/"MRPT_HAS_NI_DAQmxBASE" option and
201  * correctly set the new variables to
202  * the library include directory and library file.
203  *
204  * \note As of 2013, NI seems not to support compiling 64bit programs, so you
205  * can must build MRPT for 32bits if you need this class.
206  *
207  * \ingroup mrpt_hwdrivers_grp
208  */
210  public CGenericSensor
211 {
213  public:
214  /** Constructor */
216 
217  /** Destructor */
218  ~CNationalInstrumentsDAQ() override;
219 
220  /** Setup and launch the DAQ tasks, in parallel threads.
221  * Access to grabbed data with CNationalInstrumentsDAQ::readFromDAQ() or
222  * the standard CGenericSensor::doProcess() */
223  void initialize() override;
224 
225  /** Stop the grabbing threads for DAQ tasks. It is automatically called at
226  * destruction. */
227  void stop();
228 
229  // See docs in parent class
230  void doProcess() override;
231 
232  /** Receives data from the DAQ thread(s). It returns a maximum number of one
233  * observation object per running grabber threads, that is, per each DAQmx
234  * "task".
235  * This method MUST BE CALLED in a timely fashion by the user to allow the
236  * proccessing of incoming data. It can be run in a different thread safely.
237  * This is internally called when using the alternative
238  * CGenericSensor::doProcess() interface.
239  * No observations may be returned if there are not samples enough yet
240  * from any task.
241  */
242  void readFromDAQ(
243  std::vector<mrpt::obs::CObservationRawDAQ::Ptr>& outObservations,
244  bool& hardwareError);
245 
246  /** Set voltage outputs to all the outputs in an AOUT task
247  * For the meaning of parameters, refere to NI DAQmx docs for
248  * DAQmxBaseWriteAnalogF64()
249  * \note The number of samples in \a volt_values must match the number of
250  * channels in the task when it was initiated.
251  */
253  size_t task_index, size_t nSamplesPerChannel, const double* volt_values,
254  double timeout, bool groupedByChannel);
255 
256  /** Changes the boolean state of one digital output line.
257  * For the meaning of parameters, refere to NI DAQmx docs for
258  * DAQmxBaseWriteAnalogF64()
259  * \note The number of samples in \a volt_values must match the number of
260  * channels in the task when it was initiated.
261  */
263  size_t task_index, bool line_value, double timeout);
264 
265  /** Returns true if initialize() was called and at least one task is
266  * running. */
267  bool checkDAQIsWorking() const;
268 
269  /** Each of the tasks to create in CNationalInstrumentsDAQ::initialize().
270  * Refer to the docs on config file formats of
271  * mrpt::hwdrivers::CNationalInstrumentsDAQ to learn on the meaning
272  * of each field. Also, see National Instruments' DAQmx API docs online.
273  */
275  {
276  TaskDescription();
277 
278  bool has_ai{false}, has_ao{false}, has_di{false}, has_do{false};
279  bool has_ci_period{false}, has_ci_count_edges{false},
282 
283  /** Sample clock config: samples per second. Continuous (infinite)
284  * sampling is assumed. */
285  double samplesPerSecond{1000.0};
286  /** Sample clock source: may be empty (default value) for some channels.
287  */
288  std::string sampleClkSource;
289  /** (Default=0) From NI's docs: The number of samples the buffer can
290  * hold for each channel in the task. Zero indicates no buffer should be
291  * allocated. Use a buffer size of 0 to perform a hardware-timed
292  * operation without using a buffer. */
293  uint32_t bufferSamplesPerChannel{200000};
294  /** (Default=1000) The number of samples to grab at once from each
295  * channel. */
296  uint32_t samplesPerChannelToRead{1000};
297  /** (Default="task###") */
298  std::string taskLabel;
299 
300  /** Analog inputs */
301  struct desc_ai_t
302  {
303  desc_ai_t() = default;
304 
305  std::string physicalChannel, terminalConfig{"DAQmx_Val_NRSE"};
306  double minVal{-10}, maxVal{10};
307  /** *IMPORTANT* This must be the total number of channels listed in
308  * "physicalChannel" (e.g. 4 for "Dev1/ai0:3") */
309  unsigned int physicalChannelCount{0};
310  } ai;
311 
312  /** Analog outputs */
313  struct desc_ao_t
314  {
315  desc_ao_t() = default;
316  std::string physicalChannel;
317  /** *IMPORTANT* This must be the total number of channels listed in
318  * "physicalChannel" (e.g. 1 for "Dev1/ao0") */
319  unsigned int physicalChannelCount{0};
320  double minVal{-10}, maxVal{10};
321  } ao;
322 
323  /** Digital inputs (di) */
324  struct desc_di_t
325  {
326  /** The digital line (for example "Dev1/port0/line1") */
327  std::string line;
328  } di;
329 
330  /** Digital outs (do) */
331  struct desc_do_t
332  {
333  /** The digital line (for example "Dev1/port0/line1") */
334  std::string line;
335  } douts;
336 
338  {
339  desc_ci_period_t() = default;
340 
341  std::string counter, units, edge;
342  double minVal{0}, maxVal{0};
343  double measTime{0};
344  int divisor{1};
345  }
346  /** Counter: period of a digital signal */
347  ci_period;
348 
349  /** Counter: period of a digital signal */
351  {
352  desc_ci_count_edges_t() = default;
353 
354  std::string counter, edge, countDirection{"DAQmx_Val_CountUp"};
355  int initialCount{0};
356  } ci_count_edges;
357 
358  /** Counter: measure the width of a digital pulse */
360  {
361  desc_ci_pulse_width_t() = default;
362  std::string counter, units, startingEdge;
363  double minVal{0}, maxVal{0};
364  } ci_pulse_width;
365 
366  /** Counter: uses a linear encoder to measure linear position */
368  {
369  desc_ci_lin_encoder_t() = default;
370 
372  bool ZidxEnable{false};
373  double ZidxVal{0};
374  double distPerPulse{0.1};
375  double initialPos{0};
376  } ci_lin_encoder;
377 
378  /** Counter: uses an angular encoder to measure angular position */
380  {
381  desc_ci_ang_encoder_t() = default;
382 
384  bool ZidxEnable{false};
385  double ZidxVal{0};
386  int pulsesPerRev{512};
387  double initialAngle{0};
388  int decimate{1}, decimate_cnt{0};
389  } ci_ang_encoder;
390 
391  /** Output counter: digital pulses output */
393  {
394  desc_co_pulses_t() = default;
395 
396  std::string counter, idleState{"DAQmx_Val_Low"};
397  double initialDelay{0}, freq{1000}, dutyCycle{0.5};
398  } co_pulses;
399 
400  }; // end of TaskDescription
401 
402  /** Publicly accessible vector with the list of tasks to be launched upon
403  * call to CNationalInstrumentsDAQ::initialize().
404  * Changing these while running will have no effects.
405  */
406  std::vector<TaskDescription> task_definitions;
407 
408  protected:
409  /** See the class documentation at the top for expected parameters */
411  const mrpt::config::CConfigFileBase& configSource,
412  const std::string& iniSection) override;
413 
414  private:
415  /** A buffer for doProcess */
416  std::vector<mrpt::obs::CObservationRawDAQ::Ptr> m_nextObservations;
417 
419  {
420  TInfoPerTask();
421 
422  void* taskHandle{nullptr};
423  std::thread hThread;
424 
425  std::unique_ptr<mrpt::io::CPipeReadEndPoint> read_pipe;
426  std::unique_ptr<mrpt::io::CPipeWriteEndPoint> write_pipe;
427 
428  bool must_close{false}, is_closed{false};
429  std::atomic<int> new_obs_available;
430 
431  /** A copy of the original task description that generated this thread.
432  */
434  };
435 
436  std::list<TInfoPerTask> m_running_tasks;
437 
438  /** Method to be executed in each parallel thread. */
439  void grabbing_thread(TInfoPerTask& ipt);
440 
441 }; // end class
442 
443 } // namespace mrpt::hwdrivers
A generic interface for a wide-variety of sensors designed to be used in the application RawLogGrabbe...
Each of the tasks to create in CNationalInstrumentsDAQ::initialize().
void loadConfig_sensorSpecific(const mrpt::config::CConfigFileBase &configSource, const std::string &iniSection) override
See the class documentation at the top for expected parameters.
std::string line
The digital line (for example "Dev1/port0/line1")
struct mrpt::hwdrivers::CNationalInstrumentsDAQ::TaskDescription::desc_co_pulses_t co_pulses
Contains classes for various device interfaces.
void doProcess() override
This method will be invoked at a minimum rate of "process_rate" (Hz)
bool checkDAQIsWorking() const
Returns true if initialize() was called and at least one task is running.
void initialize() override
Setup and launch the DAQ tasks, in parallel threads.
struct mrpt::hwdrivers::CNationalInstrumentsDAQ::TaskDescription::desc_ci_ang_encoder_t ci_ang_encoder
TaskDescription task
A copy of the original task description that generated this thread.
struct mrpt::hwdrivers::CNationalInstrumentsDAQ::TaskDescription::desc_ci_pulse_width_t ci_pulse_width
This class allows loading and storing values and vectors of different types from a configuration text...
struct mrpt::hwdrivers::CNationalInstrumentsDAQ::TaskDescription::desc_do_t douts
std::string sampleClkSource
Sample clock source: may be empty (default value) for some channels.
void writeDigitalOutputTask(size_t task_index, bool line_value, double timeout)
Changes the boolean state of one digital output line.
Versatile class for consistent logging and management of output messages.
struct mrpt::hwdrivers::CNationalInstrumentsDAQ::TaskDescription::desc_di_t di
void stop()
Stop the grabbing threads for DAQ tasks.
void grabbing_thread(TInfoPerTask &ipt)
Method to be executed in each parallel thread.
An interface to read from data acquisition boards compatible with National Instruments "DAQmx Base" o...
std::vector< mrpt::obs::CObservationRawDAQ::Ptr > m_nextObservations
A buffer for doProcess.
void writeAnalogOutputTask(size_t task_index, size_t nSamplesPerChannel, const double *volt_values, double timeout, bool groupedByChannel)
Set voltage outputs to all the outputs in an AOUT task For the meaning of parameters, refere to NI DAQmx docs for DAQmxBaseWriteAnalogF64()
uint32_t bufferSamplesPerChannel
(Default=0) From NI&#39;s docs: The number of samples the buffer can hold for each channel in the task...
void readFromDAQ(std::vector< mrpt::obs::CObservationRawDAQ::Ptr > &outObservations, bool &hardwareError)
Receives data from the DAQ thread(s).
struct mrpt::hwdrivers::CNationalInstrumentsDAQ::TaskDescription::desc_ci_count_edges_t ci_count_edges
std::string line
The digital line (for example "Dev1/port0/line1")
#define DEFINE_GENERIC_SENSOR(class_name)
This declaration must be inserted in all CGenericSensor classes definition, within the class declarat...
struct mrpt::hwdrivers::CNationalInstrumentsDAQ::TaskDescription::desc_ci_lin_encoder_t ci_lin_encoder
uint32_t samplesPerChannelToRead
(Default=1000) The number of samples to grab at once from each channel.
std::vector< TaskDescription > task_definitions
Publicly accessible vector with the list of tasks to be launched upon call to CNationalInstrumentsDAQ...
unsigned int physicalChannelCount
IMPORTANT This must be the total number of channels listed in "physicalChannel" (e.g.
struct mrpt::hwdrivers::CNationalInstrumentsDAQ::TaskDescription::desc_ai_t ai
std::unique_ptr< mrpt::io::CPipeWriteEndPoint > write_pipe
struct mrpt::hwdrivers::CNationalInstrumentsDAQ::TaskDescription::desc_ci_period_t ci_period
Counter: period of a digital signal.
unsigned int physicalChannelCount
IMPORTANT This must be the total number of channels listed in "physicalChannel" (e.g.
double samplesPerSecond
Sample clock config: samples per second.
std::unique_ptr< mrpt::io::CPipeReadEndPoint > read_pipe
struct mrpt::hwdrivers::CNationalInstrumentsDAQ::TaskDescription::desc_ao_t ao
Counter: uses an angular encoder to measure angular position.



Page generated by Doxygen 1.8.14 for MRPT 1.9.9 Git: c7a3bec24 Sun Mar 29 18:33:13 2020 +0200 at dom mar 29 18:50:38 CEST 2020