Digital I/O

Mgl has some functions to handle digital I/O with a National Instruments card (e.g. NI USB 6501) that can be used to read and write digital I/O signals and sine wave analog output. These can be useful to synch to an MR scanner or control an external eye tracker. These functions all live in the directory:

mgl/utils/readDigPort

These need to be compiled specially, in mgl 2.0 by running (earlier versions, just go mex by hand):

mglMake('digio');

You will need to have downloaded the National Instruments drivers (see next section) to compile and use these functions.

Note that these functions are now compatible with both 32-bit and 64-bit Matlab even though the NI software (NI-DAQmx Base is not actually 64-bit compliant on Mac). See here for more details.

How to set up a National Instruments card

You can use a National Instruments card for digital I/O with mgl by doing the following:

  1. Download NI-DAQmx Base. You should use the latest version, as of this writing 3.7.0. You may need to make a free account with NI.
  2. Make sure that the device (e.g. NI USB-6501 or NI USB-6211) has up-to-date firmware, by running FWUpdate (included in Ni-DAQmx Base/bin)
  3. Restart matlab if you have already run readDigPort (the program has to reinit the driver). Also, note that it is best to run matlab without the desktop (matlab -nodesktop) so that you can see all the console messages described below.
  4. Documentation is installed and should be available from the following location
    file:///Applications/National%20Instruments/NI-DAQmx%20Base/documentation/docsource/daqmxbasecfuncindex.htm
  5. Follow the instructions above to compile mglDigIO and related functions.
  6. You should connect your input and output digital lines to the proper port and lines on your NI card that you want. Note that by default mglDigIO is set up to read from port 2 and write to port 1 of your NI card (but you can configure this when you run the mglDigiO('init') command. You should reference your manual for the pinouts on the board you use. For the NI-6501 see here. We usually connect one digital input to: Pin 1 (black, digital ground) Pin 16 (red, port 2 input bit 0) and one digital output to: Pin 32 (black, digital ground) and Pin 27 (red, port 1 output bit 0).
  7. Run mglDigIO('init') and you should see the following:
    >> mglDigIO('init')
    No matching processes belonging to you were found
    (mglStandaloneDigIO) Initializing NI device with digin port: Dev1/port2 digout port: Dev1/port1. End with mglDigIO('quit').
    (mglStandaloneDigIO) Successfully initialized NI device
    (mglStandaloneDigIO) New connection made: 0
    (mglPrivateDigIO) DigIO is running.
  8. You should be able to read digin events using the digin command (note the first time it will report if any of the lines are high, but not report anything for ports that are low).
    >> mglDigIO('digin')
    
    ans = 
    
        type: [1 1 1 1 1 1 1 1]
        line: [0 1 2 3 4 5 6 7]
        when: [934383.731613 934383.731624 934383.731625 934383.731626 934383.731626 934383.731628 934383.731629 934383.731629]
    

You can set up to read digial pulses for the task code in mgl 2.0 by setting digin using mglEditScreenParams.

mglDigIO

availability: mgl 2.0 Mac OS X only
usage: mglDigIO(command,<arg1>,<arg2>)
purpose: This is a mac specific command that is used to control a NI digital IO board. It has been tested with the NI USB 6501. It runs as a thread (on 64-bit runs a separate process and communicates via a socket: see below) that reads digital port 2 and logs any change in state (either up or down). It can also be used to set digital lines on port 1 at a time of your choosing. It is used by the task code if you set mglEditScreenParams to use digin. It is the preferred way of dealing with digital I/O since it keeps excellent timing. Note that if you are trying to read events up to about 250Hz (e.g. a square wave of 250Hz), you should be able to read all events without fail. Faster than that at around 500Hz you will likely start dropping events (this is likely due to how fast the NI-DAQ mxBase driver can pool the device). To use this function, you will need to compile it using mglMake('digin');

Here are thte commands it accepts:

command purpose
1:'init' Init the digIO thread. You need to run this before anything else will work. You can optional specify input and output ports which default to 1 and 2 respectively: mglDigIO('init',inputPortNum,outputPortNum); You can also specify the device number using: mglDigIO('init',inputPortNum,outputPortNum,inputDevnum,outputDevnum). You can call init with different port numbers to reset what ports you want to listen/write to/from without calling quit inbetween.
2:'digin' Returns all changes on the input digital port
3:'digout' Set the output digital port a time of your choosing. This takes 2 other values. The time in seconds that you want the digital port to be set. And the value you want it to be set too. Time can be either an absolute time returned by mglGetSecs or it can be relative to now if it is a negative value: mglDigIO('digout',-5,0) → Sets the output port to 0 five secs from now.
4:'list' Lists status and all pending digout events
5:'ao' Sets the output port to produce a sine wave and then return to 0, you call it with parameters time (like digout above), channel (0 or 1 for A0 or A1), frequency,amplitude (volts peak - it will produce a sine wave that goes from -amplitude to amplitude) and duration in seconds. For example: mglDigIO('ao',-1,0,500,2.5,1);You can optionally set the sample rate (default is 250000 samples/second): mglDigIO('ao',-1,0,500,2.5,1,100000); And you can specify the device number (below will use the default sample rate, and use dev2/ao0): mglDigIO('ao',-1,0,500,2.5,1,[],2);For more info see below
0:'quit' Closes the nidaq ports, after this you won't be able to run other commands. Note that this does not shutdown the digIO thread. The reason for this is that the NIDAQ library is not thread safe, so you can only call its functions from one thread, so to be able to keep starting and stopping reading from the card, the thread is set to continue to run, and quit simply shuts down the nidaq tasks and stops logging events. After you call quit, you can use init again to restart reading/writing. If you need to shutdown the thread, use 'shutdown'
-1:'shutdown' Quits the digIO thread if it is running, after this you won't be able to run other commands. If you plan on starting and stopping digIO collection, you should use init and quit rather than shutdown

Analog output using mglDigIO

mglDigIO supports limited analog output functionality. We have used this with NI USB-6211 which has two analog output ports that can run at 250kHz. At this moment, all that is supported is the writing of a sine wave of specified amplitude and frequency. To set it up, you need to connect the NI USB-6211 output pins correctly. Follow the information in the Pinout diagram for your device. For analog output, we connect to Ground Pin 14 (black) and AO 0 Pin 12 (red) for the Analog output 0, and for analog output 1 we use the same ground pin and AO 1 Pin 13 (red). We also connect digital output on Pin 5 Digital Ground (black) and Pin 6 (red) to get output on Port 1 bit 0. For digital input, we use Pin 5 (black, ground) and Pin 1 (red). Note that to use the input, you will need to set the correct port number (since our default is to use port 2, and for this device input is on port 0).

So, to initialize mglDigIO you would do (note port settings for input on port 0 - default is port 2):

mglDigIO('init',0);

Then, for example, to output a sine wave 4 seconds from now which goes from -2 to 2 volts at 500HZ for 3 seconds on analog output port 0, you would do:

mglDigIO('ao',-4,0,500,2,3);

Note the following limitation. You must start one event at least 25 ms after the last event even if they are on different output channels. That is, you cannot have staggered output going at the same time on different channels. (the 25 ms is to give enough set up time to so set the new output state). So, you can show a sine wave on one channel followed by a sine wave on another channel like this:

mglDigIO('ao',-4,0,500,2,3);
mglDigIO('ao',-8,1,250,5,3);

But, the following will give an error and not run the second sine wave:

% This will not work!!!
mglDigIO('ao',-4,0,500,2,3);
mglDigIO('ao',-7,1,250,5,3);

However, you can produce a sine wave of the same frequency on both output channels at the same time for the same duration with different amplitudes. The following will produce a sine wave in 4 seconds at 500Hz for a duration of 3 seconds, with the sine wave going from -2 to 2 volts on analog output channel 0 and -4 to 4 volts on analog output channel 1:

mglDigIO('ao',-4,[0 1],500,[2 4],3);

But, for the time being you can't have different frequencies, different starting times or different durations. The reason for this is because of the NI-DAQmx Base API. It does not seem to allow only setting one analog output task at a time and gives me an error if I try to load the sine wave data for a second independently created output task even if it is on a different channel. In principle we could workaround this by loading onto the NI card the whole buffer of what we want to present for each channel (like having one start with 0 and then the sine wave and the other one having a different delay followed by a different sine wave). However, this is not what we have implemented now. Right now, we just load one cycle of a sine wave on to the card and then have the card repeat that over again for the desired duration. This is why we cannot have different frequencies (they would require buffers of different lengths).

You can also change the sampling frequency. This might be useful if you do not need a lot of time resolution on the signal produced since it requires less memory transfer on to the card:

mglDigIO('ao',-4,0,500,2,3,100000);

You can also specify a different device (e.g. dev2/ao0 would be the 2nd NI device connected to your computer - not sure how you are supposed to know which device is which). This could be useful, for example, if you want to have one device for digital io and another for analog:

mglDigIO('init');
mglDigIO('ao',-4,0,500,2,3,100000,2);
mglDigIO('digout',-1,255);

The above should initialize a digital input/output for Dev1 and analog output on Dev2.

writeDigPort

usage: writeDigPort(portNum,val);
purpose: write an ouput to the National Instruments board. portNum defaults to 2, to write from Dev1/port2. The first time you read it needs to open the port to the NI device which can take some time. Subsequent calls will be faster. Note that you can only open one port at a time, so if you need to read from two different ports it will always be closing and reopening the ports which will cause a performance hit (consider rewriting the code to keep multiple ports open if you need this). Also, if you want to switch between reading and writing on a single port, you will need to manually close the port in between read/write calls by setting portNum = -1 (see below).

portNum can also be set to:

-1 closes any open port
-2 displays which port (if any) is open.

Note that in the distribution, writeDigPort is not compiled. It always returns 0. To use it to read your NI card, you will need to mex readDigPort.c, this requires you to install the NI-DAQmx Base Frameworks.

readDigPort

usage: readDigPort(<portNum>)
purpose: read the National Instruments board digital input. portNum defaults to 1, to read from Dev1/port1. The first time you read it needs to open the port to the NI device which can take some time. Subsequent calls will be faster. Note that you can only open one port at a time, so if you need to read from two different ports it will always be closing and reopening the ports which will cause a performance hit (consider rewriting the code to keep multiple ports open if you need this). Also, if you want to switch between reading and writing on a single port, you will need to manually close the port in between read/write calls by setting portNum = -1 (see below).

portNum can also be set to:

-1 closes any open port
-2 displays which port (if any) is open.

Note that: in the distribution, readDigPort is not compiled. It always returns 0. To use it to read your NI card, you will need to mex readDigPort.c, this requires you to install the NI-DAQmx Base Frameworks.

64-bit

The NI-DAQmx Base library is currently (4/20/2013) available only for 32-bit (Note that version 3.6 will run on a 64 bit platform, but you can't create 64 applications). To get around this on 64-bit Matlab, we run a separate function called mglStandaloneDigIO and communicate with that function through a socket. This is all done in the background for you so there is nothing that you have to do if you use mglDigIO.

The way it works is as follows. When you init, by doing mglDigIO('init') the function mglStandaloneDigIO is started. This function will connect to the NI card and answer commands through a socket (named .mglDigIO in your home directory). The function mglDigIO when called to get digin events or set digout events connects through the socket and communicates with mglStandaloneDigIO. This function will continue to run until you quit matlab or run mglDigIO('shutdown'). If you need to shut this function down from outside matlab, you can do from a command line:

killall mglStandaloneDigIO

Measurement Computing USB-7204

Measurement Computing makes a USB based digital I/O device with a 64 bit library for Mac which potentially could be a replacement for National Instruments which does not have a 64-bit library. However, we found that the driver needs to have c# support through the Mono Framework and were not able to find a way to make this compatible with Matlab through a Mex file. For reference, the driver can be downloaded from here.

The hardware manual is: hardware manual The software manual is: software manual.