Difference between revisions of "NI-DAQ Cards on Linux"

From Mech
Jump to navigationJump to search
(New page: ==Overview== Project by: James Yeung, Master in Electrical and Computer Engineering, 2010.<br> Last updated: April 23, 2010 The goal of this project was to get the National Instruments Da...)
 
 
(28 intermediate revisions by 2 users not shown)
Line 1: Line 1:
==Overview==
==Overview==
Project by: James Yeung, Master in Electrical and Computer Engineering, 2010.<br>
Project by: James Yeung, Master in Electrical and Computer Engineering, 2010.<br>
Last updated: April 23, 2010
Last updated: June 11, 2010


The goal of this project was to get the National Instruments Data Acquisition Cards to work on Linux. More specifically, the two cards that will be documented here are:
The goal of this project was to get the National Instruments Data Acquisition Cards to work on Linux. More specifically, the two cards that will be documented here are:
* [http://sine.ni.com/nips/cds/view/p/lang/en/nid/14130 NI PCI-6220 16-Bit, 250 kS/s, 16 Analog Inputs]
* [http://sine.ni.com/nips/cds/view/p/lang/en/nid/14130 NI PCI-6220] 16-Bit, 250 kS/s, 16 Analog Inputs
* [http://sine.ni.com/nips/cds/view/p/lang/en/nid/10702 NI PCI-6713 12-Bit, 1 MS/s per Channel Analog Output Board]
* [http://sine.ni.com/nips/cds/view/p/lang/en/nid/10702 NI PCI-6713] 12-Bit, 1 MS/s per Channel Analog Output Board


This wiki document assumes that you have real-time patched SUSE 11.0 operating system installed. You can follow the guide [http://hades.mech.northwestern.edu/index.php/Real-Time_Linux_for_TrackCam here].
This wiki document assumes that you have real-time patched SUSE 11.0 operating system installed. You can follow the guide [[Real-Time Linux for TrackCam|here]].


==Installing Drivers and C-API==
==Installing Drivers and C-API==
Line 14: Line 14:
===Instructions===
===Instructions===
<ol>
<ol>
<li>Download the NI-DAQmx 8.0.1
<li>Download the NI-DAQmx 8.0.1 [http://joule.ni.com/nidu/cds/view/p/id/1194 here]
<ul>
<ul>
<li>The file that you download is an ISO, or an image of a disk. You could burn it on a CD and run it from a physical disk, but we will be mounting the ISO virtually.</li>
<li>In this guide, we will be using a 64-bit version.</li>
<li>Follow on screen instructions and note the root password that you set.</li>
<li>When prompted about partition setup, be sure to use file system format “ext3” for the swap and home partitions. The version of the kernel that we will be building does not properly support “ext4” (or higher).</li>
</ul>
</ul>
</li>
</li>


<li>Install necessary supporting packages.
<li>Check what is the newest version of the kernel that is supported by RTLinux and other applications/drivers that you will be using.
<ul>
<li>For RTLinux, you can check here: http://www.kernel.org/pub/linux/kernel/projects/rt/</li>
<li>In this guide, we will be using version 2.6.24.7, the highest kernel version that our frame grabber software was tested on.</li>
</ul>
</li>

<li>Download the vanilla kernel and the RTLinux patch
<ul>
<ul>
<li>Open a terminal window. GNOME Terminal would do.</li>
<li>Open a terminal window. GNOME Terminal would do.</li>
<li>Go into superuser mode. You will need to know the root password.</li>
<li>Use zypper to install the packages.</li>
>> zypper in kernel-source-rt kernel-syms-rt
>> su
<li>Go into the directory where source code is commonly stored.</li>
>> cd /usr/src
<li>Download the vanilla kernel.</li>
>> wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.24.7.tar.bz2
<li>Download the RTLinux patch for the matching kernel.</li>
>> wget http://www.kernel.org/pub/linux/kernel/projects/rt/ older/patch-2.6.24.7-rt17.bz2
</ul>
</ul>
</li>
</li>


<li>Configure SUSE to startup in 4G memory mode.
<li>Unpack the packages</li>
>> tar -xvjf linux-2.6.24.7.tar.bz2
>> bunzip2 patch-2.6.24.7-rt17.bz2

<li>Make symbolic link to new directory</li>
>> rm -f linux
>> ln -fs linux-2.6.24.7 linux

<li>Copy the config file provided with the menable driver.</li>
>> cd linux
>> cp /home/lims/Download/menable/menable_linuxdrv_3.9.10/ 2.6.24.7-rt17/CORE2_x86_64/.config .config

<li>Install needed packages</li>
>> zypper in make patch gcc gtk2 gtk2-devel libglade2 libglade2-devel glib2 glib2-devel mkinitrd

<li>Apply the RTLinux patch. (Note that p1 has a one, not L)</li>
>> cat ../patch-2.6.29.6-rt24 | patch -p1

<li>Make oldconfig</li>
>> make oldconfig
<ul>
<ul>
<li>When prompted, use default by pressing enter.</li>
<li>This is not necessary if your system is 32-bit.</li>
<li>Go to the grub folder.</li>
>> cd /boot/grub
<li>Add "set mem=4096" to the entry.</li>
>> vi menu.lst
<li> Find the entry that you normally boot into and add "mem=4096M" to the end of the line that starts with "kernel".</li>
</ul>
</ul>
</li>
</li>


<li>Configure the config file through gconfig</li>
<li>Restart the computer with 4G memory mode.</li>
>> make gconfig
<ul>
<li>Make sure the following sections are untouched:
<ol>
<li>Processor type and features</li>
<ul>
<li>Except you need to set "High Resolution Timer Support" to "Yes".</li>
</ul>
<li>Bus options</li>
<li>Kernel hacking</li>
</ol>
<li>If you know which modules are needed for your hardware, enable them.</li>
<li>If you don't know which modules are needed for your hardware, compile the kernel and error messages from your attempt to install will give you a better idea of which modules need to be enabled.</li>
</ul>

<li>Save and close out of gconfig</li>


<li>Install NI-DAQmx</li>
<li>Need to change “getline” to “parseline” in scripts/unifdef.c (There are 3 getline's)
>> cd ~/Downloads/
>> tar
>> cd nidaqmx801
>> mkdir /mnt/daqmx
>> mount -t iso9660 -o loop nidaqmx801.iso /mnt/daqmx
>> cp /mnt/daqmx/disk .
>> cd disk
>> sudo ./INSTALL
<ul>
<ul>
<li>Type “vi scripts/unifdef.c" to view and edit the file.</li>
<li> Follow the prompts to install. </li>
<li>Type “/getline” to search for “getline”</li>
<li>Hit “i” to get into insert mode</li>
<li>Change “getline” to “parseline”</li>
<li>Hit the “Escape” key to get out of insert mode</li>
<li>Search again until all getlines are changed</li>
<li>Type “:wq” to save and quit</li>
</ul>
</ul>
</li>
</li>


<li>Restart the computer.
<li>Need to change “=r” to “=q” in arch/x86/boot/boot.h
<ul>
<ul>
<li>Type “vi arch/x86/boot/boot.h”</li>
<li>Open terminal and type</li>
>> nilsdev
<li>Type “112” to get to line 112</li>
<li>Hit “i” to get into insert mode</li>
<li>You should be able to see information about the NI-DAQ cards.
<li>Change “=r” to “=q”</li>
<li>Hit the “Escape” key to get out of insert mode</li>
<li>Type “:wq” to save and quit</li>
</ul>
</li>

<li>Compile & install kernel. (-j 4 is to use all 4 CPU cores, should take 15 minutes)</li>
>> make -j 4
>> make -j 4 modules_install
>> make -j 4 install
<ul>
<li>If you didn't enable all required modules, you will get error messages hinting which ones you need here.</li>
</ul>

<li>Make sure “fstab” has correct paths.</li>
>> cd /etc
>> vi fstab
<ul>
<li>The first couple of lines should look something like this:</li>
/dev/sda5 swap swap defaults 0 0
/dev/sda6 / ext3 acl,user_xattr 1 1
/dev/sda7 /home ext3 acl,user_xattr 1 2
<li>If not, change the part after “/dev/” to “sda#” where # is the corresponding number to “-part#”.</li>
</ul>

<li>You'll need to change the grub config file to match the changes in fstab.
<ul>
<li>Grub is the software that controls which OS to boot into during boot up.</li>
<li>Files to configure Grub are located at /boot/grub/</li>
<li>You may also want to edit the menu.lst file to your desire.</li>
</ul>
</ul>
</li>
</li>
</ol>
</ol>


===Configuration Guide===
==How To Install MicroEnable Frame Grabber Drivers/Software==
There is a configuration guide (ConfigurationGuide.html) included in the drivers that you downloaded from step 1. For example, you can configure the name of the card from dev0 to something more descriptive like AnalogIn. This name will also affect how you program later on.
<ul>
<li>This guide assumes that you have downloaded and untarred the following files under /home/lims/Download/menable/
<ul>
<li>menable_linuxdrv_3.9.10.tar.bz2</li>
<li>siso-rt3-meIII-3.2.1-2.i586.rpm</li>
<li>siso-rt-basesystem-1.0.0-1.i586.rpm</li>
</ul>
</li>
<li>We will be using menable_linuxdrv_3.9.10 with the 2.6.23.7-rt17 kernel.</li>
</ul>


===Drivers===
==Datasheets==
[http://hades.mech.northwestern.edu/images/1/1f/Nipci-6220_datasheet.pdf NIPCI-6220 Datasheet]
<ol>
<li>Go into the root of the driver folder.</li>
>> cd /home/lims/Download/menable/menable_linuxdrv_3.9.10
<li>We need to copy the binary objects from the subdirectory matching our kernel and architecture.</li>
>> cp 2.6.24.7-rt17/CORE2_x86_64/* .
<li>Compile</li>
>> ./compile.sh
<li>Install the compiled driver</li>
>> insmod menable.ko
<li>Confirm the install</li>
>> dmesg | tail
<ul>
<li>The output should look like this:</li>
0000:00:19.0: eth0: Link is Up 100 Mbps Full Duplex, Flow Control: RX
0000:00:19.0: eth0: 10/100 speed: disabling TSO
ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
martian source 255.255.255.255 from 129.105.69.13, on dev eth0
ll header: ff:ff:ff:ff:ff:ff:d4:9a:20:d0:13:88:08:00
eth0: no IPv6 routers present
devkit-disks-da[3482]: segfault at 18 rip 41cb5e rsp 7fff78425850 error 4
ACPI: PCI Interrupt 0000:11:00.0[A] -> GSI 20 (level, low) -> IRQ 20
menable 0000:11:00.0: microEnable III card will be called menable0
menable menable0: allocated dummy DMA area of 128 kiB
</ul>
</li>
</ol>


[http://hades.mech.northwestern.edu/images/8/87/Nipci-6220_manual.pdf NIPCI-6220 Manual]
===Software===
<ol>
<li>Go into the direction with the RPM packages.</li>
>> cd /home/lims/Download/menable


[http://hades.mech.northwestern.edu/images/9/95/Nipci-6713_datasheet.pdf NIPCI-6713 Datasheet]
<li>Install the two rpm packages.</li>
>> rpm -i siso-rt-basesystem-1.0.0-1.i586.rpm
>> rpm -i siso-rt3-meIII-3.2.1-2.i586.rpm


[http://hades.mech.northwestern.edu/images/5/5d/Nipci-6713_manual.pdf NIPCI-6713 Manual]
<li>Confirm the install
<ul>
<li>Use the following commands to see where the files should have been installed.</li>
>> rpm -qlp siso-rt-basesystem-1.0.0-1.i586.rpm
>> rpm -qlp siso-rt3-meIII-3.2.1-2.i586.rpm
<li>Go into the directories and see if they are there.</li>
</ul>
</li>
</ol>


==Timing Data==
==How To Install OpenCV on Linux==
Here are some experimental timing data.
<ol>
<li>Make sure the following packages are installed.
<ul>
<li>gcc (version 4.x)</li>
<li>cmake (version 2.6 or higher)</li>
<li>pkg-config</li>
</ul>
</li>
<li>Download module “FindOpenCV.cmake” and add it to cmake.
<ul>
<li>Module can be downloaded via: http://opencv.willowgarage.com/wiki/Getting_started?action=AttachFile&do=view&target=FindOpenCV.cmake</li>
<li>Download and place file into /usr/share/cmake/Modules.</li>
</ul>
</li>
<li>Download OpenCV source code, configure, compile and install.
<ul>
<li>Source can be downloaded via: http://sourceforge.net/projects/opencvlibrary/files/opencv-unix/2.0/</li>
<li>./configure</li>
<li>make</li>
<li>make install</li>
</ul>
</li>
<li>(Optional) Go through the “Hello World” tutorial to make sure it works.
<ul>
<li>http://opencv.willowgarage.com/wiki/Getting_started</li>
</ul>
</li>
</ol>


==Benchmarking==
===Analog===
Time it takes to read x analog inputs.<br>
This section was an attempt to see how "real-time" the operating system is.
2 = 27.6-42.5us (typically 28.8us)<br>
3 = 41.9-55.3us (typically 43.6us)<br>
4 = 55.9-72.9us (typically 57.8us)<br>
5 = 69.8-86.3us (typically 72.6us)<br>
6 = 84.3-100.5us (typically 87.1us)<br>
7 = 98.0-124.0us (typically ~101us)<br>
8 = 112.0-130.4us (typically ~115us)<br>
<br>
Time it takes to write x analog outputs.<br>
2 = 12.8-23.2us (typically 13.1us)<br>
3 = 13.1-23.2us (typically 13.1us)<br>
4 = 13.4-23.8us (typically 13.7us)<br>
5 = 15.1-26.0us (typically 15.4us)<br>
6 = 15.9-27.9us (typically 16.2us)<br>
7 = 17.6-29.9us (typically 17.9us)<br>
8 = 19.0-32.1us (typically 19.5us)<br>
===Digital===
Time it takes to read x digital inputs.<br>
1 = 12.0us-22.3us (typically 12.0us)<br>
8 = 12.6us-24.9us (typically 12.6us)<br>
<br>
Time it takes to write x digital outputs.<br>
1 = 12.3us-21.5us (typically 12.6us)<br>
8 = 13.1us-24.0us (typically 13.1us)<br>
<br>
===Encoder===
Time it takes to read x encoder inputs.<br>
1 = 14.2us-27.6us (typically 14.5us)<br>
<br>


===Cyclictest===
==Example Code==
Cyclictest is program available on the rt-wiki for testing the latency of commands on your operating system. All the code and instructions are available on their website.


To compile: gcc main.c -o main -lnidaqmx
http://rt.wiki.kernel.org/index.php/Cyclictest

===Homemade Test===
I found a "Hello World" code for real-time applications on the -rt Linux patch online. I then modified the code such that it would have the highest possible priority (1) and that it would take a snapshot of the timer every 10ms.


===How to Read Analog Inputs===
<pre>
<pre>
#include <stdlib.h>
#include <stdio.h>
#include <stdio.h>
#include <NIDAQmx.h>
#include <time.h>
#include <time.h>
#include <sys/time.h>
#include <sched.h>
#include <sys/mman.h>
#include <string.h>


#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else
#define MY_PRIORITY (1 /* we use 1 as the PRREMPT_RT use 50
as the priority of kernel tasklets
and interrupt handler by default */


int main(void){
#define MAX_SAFE_STACK (8*1024) /* The maximum stack size which is
int32 error=0;
guranteed safe to access without
TaskHandle taskHandle=0;
faulting */
int32 read;
float64 data;
char errBuff[2048]={'\0'};
int ii;


/*********************************************/
#define NSEC_PER_SEC (1000000000) /* The number of nsecs per sec. */
// DAQmx Configure Code
/*********************************************/
DAQmxErrChk(DAQmxCreateTask("Task",&taskHandle));
DAQmxErrChk(DAQmxCreateAIVoltageChan(taskHandle,"AnalogIn/ai0","ChannelName",DAQmx_Val_RSE,-10.0,10.0,DAQmx_Val_Volts,NULL));
// To read more analog inputs, simply add more channels to the task.


/*********************************************/
void stack_prefault(void) {
// DAQmx Start Code
unsigned char dummy[MAX_SAFE_STACK];
/*********************************************/
memset(&dummy, 0, MAX_SAFE_STACK);
DAQmxErrChk(DAQmxStartTask(taskHandle));
return;
}


/*********************************************/
int main(int argc, char* argv[]) {
// DAQmx Read Code
/*********************************************/
for(ii = 0; ii < 2000; ii++){
DAQmxErrChk(DAQmxReadAnalogF64(taskHandle,1,10.0,DAQmx_Val_GroupByChannel,&data,1,&read,NULL));
printf("%4d\t%f\n", ii, data);
}


Error:
//int time[1002];
if( DAQmxFailed(error) )
//int rc[1002];
DAQmxGetExtendedErrorInfo(errBuff,2048);
if( taskHandle!=0 ) {
/*********************************************/
// DAQmx Stop Code
/*********************************************/
DAQmxStopTask(taskHandle);
DAQmxClearTask(taskHandle);
}
if( DAQmxFailed(error) )
printf("DAQmx Error: %s\n",errBuff);
printf("End of program, press Enter key to quit\n");
getchar();
return 0;
}
</pre>


===How to Read Digital Inputs===
struct timespec t;
<pre>
struct timespec ti[1002];
#include <stdio.h>
struct timespec tt[1002];
#include <NIDAQmx.h>
struct timespec tf[1002];
#include <time.h>
struct sched_param param;
int interval = 10000000; /* 10ms*/


#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else
/* Declare ourself as a real time task */


int main(void){
param.sched_priority = MY_PRIORITY;
int32 error=0;
if(sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
TaskHandle taskHandle=0;
perror("sched_setscheduler failed");
int32 read;
exit(-1);
int32 readBytePerSamp;
}
int8 data;
char errBuff[2048]={'\0'};
int ii;


/*********************************************/
/* Lock memory */
// DAQmx Configure Code
/*********************************************/
if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) {
DAQmxErrChk(DAQmxCreateTask("Task",&taskHandle));
perror("mlockall failed");
DAQmxErrChk(DAQmxCreateDIChan(taskHandle,"AnalogIn/port0/line0","",DAQmx_Val_ChanPerLine));
exit(-2);
// Use the line below to add multiple lines from the same port
}
// DAQmxErrChk(DAQmxCreateDIChan(taskHandle,"AnalogIn/port0/line0:7","",DAQmx_Val_ChanPerLine));


/*********************************************/
/* Pre-fault our stack */
// DAQmx Start Code
/*********************************************/
stack_prefault();
DAQmxErrChk(DAQmxStartTask(taskHandle));


/*********************************************/
clock_gettime(CLOCK_MONOTONIC ,&t);
// DAQmx Read Code
/* start after one second */
/*********************************************/
t.tv_sec++;
for(ii = 0; ii < 2000; ii++){
DAQmxErrChk(DAQmxReadDigitalLines(taskHandle,1,10.0,DAQmx_Val_GroupByScanNumber,&data,1,&read,&readBytePerSamp,NULL));
int ii = 0;
printf("%4d\t%d\n", ii, data);
while(ii < 1002) {
}
tt[ii].tv_nsec = t.tv_nsec; //target time stamp
clock_gettime(CLOCK_MONOTONIC ,&ti[ii]); //time before releasing CPU


Error:
/* release CPU until target time stamp is reached */
if( DAQmxFailed(error) )
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &t, NULL);
DAQmxGetExtendedErrorInfo(errBuff,2048);
if( taskHandle!=0 ) {
/* do the stuff */
/*********************************************/
clock_gettime(CLOCK_MONOTONIC ,&tf[ii]); //time right after waking up
// DAQmx Stop Code
ii++;
/*********************************************/

DAQmxStopTask(taskHandle);
/* calculate next target */
DAQmxClearTask(taskHandle);
t.tv_nsec += interval;
}
while (t.tv_nsec >= NSEC_PER_SEC) {
if( DAQmxFailed(error) )
t.tv_nsec -= NSEC_PER_SEC;
printf("DAQmx Error: %s\n",errBuff);
t.tv_sec++;
printf("End of program, press Enter key to quit\n");
}
getchar();
}
return 0;
for(int ii = 1; ii < 1002; ii++){
printf("%d %d %d %d\t%d\n", ti[ii].tv_nsec, tt[ii].tv_nsec, tf[ii].tv_nsec, tf[ii].tv_nsec - tt[ii].tv_nsec, tf[ii].tv_nsec - tf[ii-1].tv_nsec);
}
}
}
</pre>
</pre>


===How to Write Analog Outputs===
==Results==
<pre>
To ensure that the operating system is truly "real-time", I ran the two tests (Cyclictest and Homemade Test) under two conditions, load and no load. The no load condition is simply where except for system processes, only the test programs are running. The load condition is where I compile the source code of OpenCV and the Linux kernel using a niceness of -20.
#include <stdio.h>
#include <NIDAQmx.h>
#include <time.h>


#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else
===Cyclictest===
Using the Cyclictest program, I was able to get some very nice results.


int main(void){
Arguments:
int32 error=0;
t = number of threads to work
TaskHandle taskHandle=0;
p = priority level to set
int32 written;
n = use nano_sleep
float64 data;
i = interval in microseconds
char errBuff[2048]={'\0'};
l = number of times to loop
int ii;


/*********************************************/
<ul>
// DAQmx Configure Code
<li>No Load
/*********************************************/
<ul>
DAQmxErrChk(DAQmxCreateTask("Task",&taskHandle));
<li>Priority Level = 1</li>
DAQmxErrChk(DAQmxCreateAIVoltageChan(taskHandle,"AnalogOut/ai0","ChannelName",DAQmx_Val_RSE,-10.0,10.0,DAQmx_Val_Volts,NULL));
sudo cyclictest -t1 -p 1 -n -i 1000 -l 10000
// To write more analog outputs, simply add more channels to the task.
policy: fifo: loadavg: 1.14 0.46 0.18 1/291 5891
T: 0 ( 5791) P: 1 I:1000 C: 10000 Min: 1 Act: 1 Avg: 1 Max: 712
<li>Priority Level = 2</li>
sudo cyclictest -t1 -p 2 -n -i 1000 -l 10000
policy: fifo: loadavg: 0.11 0.59 0.44 1/294 16748
T: 0 (16624) P: 2 I:1000 C: 10000 Min: 1 Act: 2 Avg: 1 Max: 5
<li>Priority Level = 80</li>
sudo cyclictest -t1 -p 80 -n -i 1000 -l 10000
policy: fifo: loadavg: 0.19 0.66 0.46 1/293 16414
T: 0 (16289) P:80 I:1000 C: 10000 Min: 2 Act: 2 Avg: 2 Max: 8
</ul>
</li>
<li>Under Load
<ul>
<li>Priority Level = 1</li>
sudo cyclictest -t1 -p 1 -n -i 1000 -l 10000
policy: fifo: loadavg: 3.52 1.16 0.44 16/331 11374
T: 0 ( 8812) P: 1 I:1000 C: 10000 Min: 1 Act: 3 Avg: 10 Max: 3093
<li>Priority Level = 2</li>
sudo cyclictest -t1 -p 2 -n -i 1000 -l 10000
policy: fifo: loadavg: 4.56 1.47 0.74 14/333 23801
T: 0 (22748) P: 2 I:1000 C: 10000 Min: 1 Act: 5 Avg: 6 Max: 17
<li>Priority Level = 80</li>
sudo cyclictest -t1 -p 80 -n -i 1000 -l 10000
policy: fifo: loadavg: 5.40 2.38 1.15 12/332 28779
T: 0 (27402) P:80 I:1000 C: 10000 Min: 1 Act: 6 Avg: 5 Max: 14
</ul>
</li>
</ul>
It is interesting to note that when using a priority level of 1 in Cyclictest, we get a much higher Max than a priority level of 2 or 80.


/*********************************************/
===Homemade Test===
// DAQmx Start Code
The results of this test can be summarized and better visualized in histograms. Below are two histograms from each of the two conditions.
/*********************************************/
DAQmxErrChk(DAQmxStartTask(taskHandle));


/*********************************************/
[[image:RTLinux_Homemade_Latency_Noload.JPG|thumb|500px|Latencies between target time stamp and recorded time stamp under no-load conditions.|left]]
// DAQmx Read Code
[[image:RTLinux_Homemade_Interval_Noload.JPG|thumb|500px|Intervals between consecutive time stamps under no-load conditions.|right]]
/*********************************************/
[[image:RTLinux_Homemade_Latency_Underload.JPG|thumb|500px|Latencies between target time stamp and recorded time stamp under load conditions.|left]]
for(ii = 0; ii < 2000; ii++){
[[image:RTLinux_Homemade_Interval_Underload.JPG|thumb|500px|Intervals between consecutive time stamps under load conditions.|right]]
data = ii % 10;
DAQmxErrChk(DAQmxWriteAnalogF64(taskHandle,1,TRUE,10.0,DAQmx_Val_GroupByChannel,&data,&written,NULL));
printf("%4d\t%f\n", ii, data);
}


Error:
if( DAQmxFailed(error) )
DAQmxGetExtendedErrorInfo(errBuff,2048);
if( taskHandle!=0 ) {
/*********************************************/
// DAQmx Stop Code
/*********************************************/
DAQmxStopTask(taskHandle);
DAQmxClearTask(taskHandle);
}
if( DAQmxFailed(error) )
printf("DAQmx Error: %s\n",errBuff);
printf("End of program, press Enter key to quit\n");
getchar();
return 0;
}
</pre>


===How to Write Digital Outputs===
<pre>
#include <stdio.h>
#include <NIDAQmx.h>
#include <time.h>


#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else


int main(void){
int32 error=0;
TaskHandle taskHandle=0;
int32 written;
int8 data;
char errBuff[2048]={'\0'};
int ii;


/*********************************************/
// DAQmx Configure Code
/*********************************************/
DAQmxErrChk(DAQmxCreateTask("Task",&taskHandle));
DAQmxErrChk(DAQmxCreateDOChan(taskHandle,"AnalogIn/port0/line0","",DAQmx_Val_ChanPerLine));
// Use the line below to add multiple lines from the same port
// DAQmxErrChk(DAQmxCreateDOChan(taskHandle,"AnalogIn/port0/line0:7","",DAQmx_Val_ChanPerLine));


/*********************************************/
// DAQmx Start Code
/*********************************************/
DAQmxErrChk(DAQmxStartTask(taskHandle));


/*********************************************/
// DAQmx Read Code
/*********************************************/
for(ii = 0; ii < 2000; ii++){
data = ii % 2;
DAQmxErrChk(DAQmxWriteDigitalLines(taskHandle,1,TRUE,10.0,DAQmx_Val_GroupByScanNumber,&data,&written,NULL));
printf("%4d\t%d\n", ii, data);
}


Error:
if( DAQmxFailed(error) )
DAQmxGetExtendedErrorInfo(errBuff,2048);
if( taskHandle!=0 ) {
/*********************************************/
// DAQmx Stop Code
/*********************************************/
DAQmxStopTask(taskHandle);
DAQmxClearTask(taskHandle);
}
if( DAQmxFailed(error) )
printf("DAQmx Error: %s\n",errBuff);
printf("End of program, press Enter key to quit\n");
getchar();
return 0;
}
</pre>


===How to Read Encoder Counts===
See page 136-137 of the [http://hades.mech.northwestern.edu/images/8/87/Nipci-6220_manual.pdf NIPCI-6220 Manual] for the ChA and ChB pins.
<pre>
#include <stdio.h>
#include <NIDAQmx.h>
#include <time.h>


#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else


int main(void){
int32 error=0;
TaskHandle taskHandle=0;
int32 read;
float64 motorAngle;
char errBuff[2048]={'\0'};
int ii;


/*********************************************/
// DAQmx Configure Code
/*********************************************/
DAQmxErrChk(DAQmxCreateTask("EncoderTask",&taskHandle));
DAQmxErrChk(DAQmxCreateCIAngEncoderChan(taskHandle,"AnalogIn/ctr0","Counter",DAQmx_Val_X4,0,0.0,DAQmx_Val_AHighBHigh,DAQmx_Val_Degrees,600,36000.0,""));


/*********************************************/
// DAQmx Start Code
/*********************************************/
DAQmxErrChk(DAQmxStartTask(taskHandle));


/*********************************************/
// DAQmx Read Code
/*********************************************/
for(ii = 0; ii < 2000; ii++){
DAQmxErrChk(DAQmxReadCounterF64(taskHandle,1,10.0,&motorAngle,1,&read,NULL));
printf("%4d\t%f\n", ii, motorAngle);
}


Error:
if( DAQmxFailed(error) )
DAQmxGetExtendedErrorInfo(errBuff,2048);
if( taskHandle!=0 ) {
/*********************************************/
// DAQmx Stop Code
/*********************************************/
DAQmxStopTask(taskHandle);
DAQmxClearTask(taskHandle);
}
if( DAQmxFailed(error) )
printf("DAQmx Error: %s\n",errBuff);
printf("End of program, press Enter key to quit\n");
getchar();
return 0;
}
</pre>


===Others===
More example code can be found in the directory "/usr/local/natinst/nidaqmx/examples/".


Full documentation on the nidaqmx API can be found at "/usr/local/natinst/nidaqmx/docs/daqmxcfunc.chm/_main.html"








































.

==Future Work==
Now that we have an operating system with sub 100us latency, our next step is to port the code for the camera over to the Linux side. Once that is done, we would like to be able to capture images at 1000fps or better and track the location of bright spots in the image.


==Useful Links==
==Useful Links==
<ul>
[http://www.linuxjournal.com/article/232 Micro-Kernel Approach]
<li>[http://decibel.ni.com/content/docs/DOC-2808 NI forum on installing drivers on openSUSE]</li>

<li>[http://zone.ni.com/devzone/cda/tut/p/id/6999 List of sample code provided by NI]</li>
[http://www.linuxjournal.com/magazine/real-time-linux-kernel-scheduler?page=0,0 Scheduling Approach]
</ul>

[http://rt.wiki.kernel.org/index.php/Main_Page Wiki on -rt Linux]

[http://rt.wiki.kernel.org/index.php/RT_PREEMPT_HOWTO A "Hello World" Example]



[[Category:Linux]]
[[Category:Linux]]

Latest revision as of 14:27, 15 June 2010

Overview

Project by: James Yeung, Master in Electrical and Computer Engineering, 2010.
Last updated: June 11, 2010

The goal of this project was to get the National Instruments Data Acquisition Cards to work on Linux. More specifically, the two cards that will be documented here are:

This wiki document assumes that you have real-time patched SUSE 11.0 operating system installed. You can follow the guide here.

Installing Drivers and C-API

The supporting software is all available on the National Instruments website. The specific package that we will need is NI-DAQmx 8.0.1. This package contains the drivers, C-API library, documentation on the library functions and example code.

Instructions

  1. Download the NI-DAQmx 8.0.1 here
    • The file that you download is an ISO, or an image of a disk. You could burn it on a CD and run it from a physical disk, but we will be mounting the ISO virtually.
  2. Install necessary supporting packages.
    • Open a terminal window. GNOME Terminal would do.
    • Use zypper to install the packages.
    • >> zypper in kernel-source-rt kernel-syms-rt
  3. Configure SUSE to startup in 4G memory mode.
    • This is not necessary if your system is 32-bit.
    • Go to the grub folder.
    • >> cd /boot/grub
    • Add "set mem=4096" to the entry.
    • >> vi menu.lst
    • Find the entry that you normally boot into and add "mem=4096M" to the end of the line that starts with "kernel".
  4. Restart the computer with 4G memory mode.
  5. Install NI-DAQmx
  6. >> cd ~/Downloads/ >> tar >> cd nidaqmx801 >> mkdir /mnt/daqmx >> mount -t iso9660 -o loop nidaqmx801.iso /mnt/daqmx >> cp /mnt/daqmx/disk . >> cd disk >> sudo ./INSTALL
    • Follow the prompts to install.
  7. Restart the computer.
    • Open terminal and type
    • >> nilsdev
    • You should be able to see information about the NI-DAQ cards.

Configuration Guide

There is a configuration guide (ConfigurationGuide.html) included in the drivers that you downloaded from step 1. For example, you can configure the name of the card from dev0 to something more descriptive like AnalogIn. This name will also affect how you program later on.

Datasheets

NIPCI-6220 Datasheet

NIPCI-6220 Manual

NIPCI-6713 Datasheet

NIPCI-6713 Manual

Timing Data

Here are some experimental timing data.

Analog

Time it takes to read x analog inputs.
2 = 27.6-42.5us (typically 28.8us)
3 = 41.9-55.3us (typically 43.6us)
4 = 55.9-72.9us (typically 57.8us)
5 = 69.8-86.3us (typically 72.6us)
6 = 84.3-100.5us (typically 87.1us)
7 = 98.0-124.0us (typically ~101us)
8 = 112.0-130.4us (typically ~115us)

Time it takes to write x analog outputs.
2 = 12.8-23.2us (typically 13.1us)
3 = 13.1-23.2us (typically 13.1us)
4 = 13.4-23.8us (typically 13.7us)
5 = 15.1-26.0us (typically 15.4us)
6 = 15.9-27.9us (typically 16.2us)
7 = 17.6-29.9us (typically 17.9us)
8 = 19.0-32.1us (typically 19.5us)

Digital

Time it takes to read x digital inputs.
1 = 12.0us-22.3us (typically 12.0us)
8 = 12.6us-24.9us (typically 12.6us)

Time it takes to write x digital outputs.
1 = 12.3us-21.5us (typically 12.6us)
8 = 13.1us-24.0us (typically 13.1us)

Encoder

Time it takes to read x encoder inputs.
1 = 14.2us-27.6us (typically 14.5us)

Example Code

To compile: gcc main.c -o main -lnidaqmx

How to Read Analog Inputs

#include <stdio.h>
#include <NIDAQmx.h>
#include <time.h>

#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else

int main(void){
	int32		error=0;
	TaskHandle	taskHandle=0;
	int32		read;
	float64		data;
	char		errBuff[2048]={'\0'};
	int		ii;

	/*********************************************/
	// DAQmx Configure Code
	/*********************************************/
	DAQmxErrChk(DAQmxCreateTask("Task",&taskHandle));
	DAQmxErrChk(DAQmxCreateAIVoltageChan(taskHandle,"AnalogIn/ai0","ChannelName",DAQmx_Val_RSE,-10.0,10.0,DAQmx_Val_Volts,NULL));
	// To read more analog inputs, simply add more channels to the task.

	/*********************************************/
	// DAQmx Start Code
	/*********************************************/
	DAQmxErrChk(DAQmxStartTask(taskHandle));

	/*********************************************/
	// DAQmx Read Code
	/*********************************************/
	for(ii = 0; ii < 2000; ii++){
		DAQmxErrChk(DAQmxReadAnalogF64(taskHandle,1,10.0,DAQmx_Val_GroupByChannel,&data,1,&read,NULL));
		printf("%4d\t%f\n", ii, data);
	}

Error:
	if( DAQmxFailed(error) )
		DAQmxGetExtendedErrorInfo(errBuff,2048);
	if( taskHandle!=0 )  {
		/*********************************************/
		// DAQmx Stop Code
		/*********************************************/
		DAQmxStopTask(taskHandle);
		DAQmxClearTask(taskHandle);
	}
	if( DAQmxFailed(error) )
		printf("DAQmx Error: %s\n",errBuff);
	printf("End of program, press Enter key to quit\n");
	getchar();
	return 0;
}

How to Read Digital Inputs

#include <stdio.h>
#include <NIDAQmx.h>
#include <time.h>

#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else

int main(void){
	int32		error=0;
	TaskHandle	taskHandle=0;
	int32		read;
	int32		readBytePerSamp;
	int8		data;
	char		errBuff[2048]={'\0'};
	int		ii;

	/*********************************************/
	// DAQmx Configure Code
	/*********************************************/
	DAQmxErrChk(DAQmxCreateTask("Task",&taskHandle));
	DAQmxErrChk(DAQmxCreateDIChan(taskHandle,"AnalogIn/port0/line0","",DAQmx_Val_ChanPerLine));
	// Use the line below to add multiple lines from the same port
	// DAQmxErrChk(DAQmxCreateDIChan(taskHandle,"AnalogIn/port0/line0:7","",DAQmx_Val_ChanPerLine));

	/*********************************************/
	// DAQmx Start Code
	/*********************************************/
	DAQmxErrChk(DAQmxStartTask(taskHandle));

	/*********************************************/
	// DAQmx Read Code
	/*********************************************/
	for(ii = 0; ii < 2000; ii++){
		DAQmxErrChk(DAQmxReadDigitalLines(taskHandle,1,10.0,DAQmx_Val_GroupByScanNumber,&data,1,&read,&readBytePerSamp,NULL));
		printf("%4d\t%d\n", ii, data);
	}

Error:
	if( DAQmxFailed(error) )
		DAQmxGetExtendedErrorInfo(errBuff,2048);
	if( taskHandle!=0 )  {
		/*********************************************/
		// DAQmx Stop Code
		/*********************************************/
		DAQmxStopTask(taskHandle);
		DAQmxClearTask(taskHandle);
	}
	if( DAQmxFailed(error) )
		printf("DAQmx Error: %s\n",errBuff);
	printf("End of program, press Enter key to quit\n");
	getchar();
	return 0;
}

How to Write Analog Outputs

#include <stdio.h>
#include <NIDAQmx.h>
#include <time.h>

#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else

int main(void){
	int32		error=0;
	TaskHandle	taskHandle=0;
	int32		written;
	float64		data;
	char		errBuff[2048]={'\0'};
	int		ii;

	/*********************************************/
	// DAQmx Configure Code
	/*********************************************/
	DAQmxErrChk(DAQmxCreateTask("Task",&taskHandle));
	DAQmxErrChk(DAQmxCreateAIVoltageChan(taskHandle,"AnalogOut/ai0","ChannelName",DAQmx_Val_RSE,-10.0,10.0,DAQmx_Val_Volts,NULL));
	// To write more analog outputs, simply add more channels to the task.

	/*********************************************/
	// DAQmx Start Code
	/*********************************************/
	DAQmxErrChk(DAQmxStartTask(taskHandle));

	/*********************************************/
	// DAQmx Read Code
	/*********************************************/
	for(ii = 0; ii < 2000; ii++){
		data = ii % 10;
		DAQmxErrChk(DAQmxWriteAnalogF64(taskHandle,1,TRUE,10.0,DAQmx_Val_GroupByChannel,&data,&written,NULL));
		printf("%4d\t%f\n", ii, data);
	}

Error:
	if( DAQmxFailed(error) )
		DAQmxGetExtendedErrorInfo(errBuff,2048);
	if( taskHandle!=0 )  {
		/*********************************************/
		// DAQmx Stop Code
		/*********************************************/
		DAQmxStopTask(taskHandle);
		DAQmxClearTask(taskHandle);
	}
	if( DAQmxFailed(error) )
		printf("DAQmx Error: %s\n",errBuff);
	printf("End of program, press Enter key to quit\n");
	getchar();
	return 0;
}

How to Write Digital Outputs

#include <stdio.h>
#include <NIDAQmx.h>
#include <time.h>

#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else

int main(void){
	int32		error=0;
	TaskHandle	taskHandle=0;
	int32		written;
	int8		data;
	char		errBuff[2048]={'\0'};
	int		ii;

	/*********************************************/
	// DAQmx Configure Code
	/*********************************************/
	DAQmxErrChk(DAQmxCreateTask("Task",&taskHandle));
	DAQmxErrChk(DAQmxCreateDOChan(taskHandle,"AnalogIn/port0/line0","",DAQmx_Val_ChanPerLine));
	// Use the line below to add multiple lines from the same port
	// DAQmxErrChk(DAQmxCreateDOChan(taskHandle,"AnalogIn/port0/line0:7","",DAQmx_Val_ChanPerLine));

	/*********************************************/
	// DAQmx Start Code
	/*********************************************/
	DAQmxErrChk(DAQmxStartTask(taskHandle));

	/*********************************************/
	// DAQmx Read Code
	/*********************************************/
	for(ii = 0; ii < 2000; ii++){
		data = ii % 2;
		DAQmxErrChk(DAQmxWriteDigitalLines(taskHandle,1,TRUE,10.0,DAQmx_Val_GroupByScanNumber,&data,&written,NULL));
		printf("%4d\t%d\n", ii, data);
	}

Error:
	if( DAQmxFailed(error) )
		DAQmxGetExtendedErrorInfo(errBuff,2048);
	if( taskHandle!=0 )  {
		/*********************************************/
		// DAQmx Stop Code
		/*********************************************/
		DAQmxStopTask(taskHandle);
		DAQmxClearTask(taskHandle);
	}
	if( DAQmxFailed(error) )
		printf("DAQmx Error: %s\n",errBuff);
	printf("End of program, press Enter key to quit\n");
	getchar();
	return 0;
}

How to Read Encoder Counts

See page 136-137 of the NIPCI-6220 Manual for the ChA and ChB pins.

#include <stdio.h>
#include <NIDAQmx.h>
#include <time.h>

#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else

int main(void){
	int32		error=0;
	TaskHandle	taskHandle=0;
	int32		read;
	float64		motorAngle;
	char		errBuff[2048]={'\0'};
	int		ii;

	/*********************************************/
	// DAQmx Configure Code
	/*********************************************/
	DAQmxErrChk(DAQmxCreateTask("EncoderTask",&taskHandle));
	DAQmxErrChk(DAQmxCreateCIAngEncoderChan(taskHandle,"AnalogIn/ctr0","Counter",DAQmx_Val_X4,0,0.0,DAQmx_Val_AHighBHigh,DAQmx_Val_Degrees,600,36000.0,""));

	/*********************************************/
	// DAQmx Start Code
	/*********************************************/
	DAQmxErrChk(DAQmxStartTask(taskHandle));

	/*********************************************/
	// DAQmx Read Code
	/*********************************************/
	for(ii = 0; ii < 2000; ii++){
		DAQmxErrChk(DAQmxReadCounterF64(taskHandle,1,10.0,&motorAngle,1,&read,NULL));
		printf("%4d\t%f\n", ii, motorAngle);
	}

Error:
	if( DAQmxFailed(error) )
		DAQmxGetExtendedErrorInfo(errBuff,2048);
	if( taskHandle!=0 )  {
		/*********************************************/
		// DAQmx Stop Code
		/*********************************************/
		DAQmxStopTask(taskHandle);
		DAQmxClearTask(taskHandle);
	}
	if( DAQmxFailed(error) )
		printf("DAQmx Error: %s\n",errBuff);
	printf("End of program, press Enter key to quit\n");
	getchar();
	return 0;
}

Others

More example code can be found in the directory "/usr/local/natinst/nidaqmx/examples/".

Full documentation on the nidaqmx API can be found at "/usr/local/natinst/nidaqmx/docs/daqmxcfunc.chm/_main.html"

Useful Links