AT-MIO-16X device driver for Linux Version 0.6 (BETA!!) Amish S. Dave 4/6/98 This documentation is Version 0.0001 (ALPHA!!) This is a device driver for the National Instruments AT-MIO-16X Data Acquisition card. This card has: 8/16 input 16 bit ADC (Analog Input) 2 * 16 bit DAC (Analog Output) 8 Digital I/O lines 5 Counter/Timers Implemented in this driver: ADC DAC DIO Warning - not implemented at all: Calibration procedures: National Instruments cannot provide the information on programming the EEPROM or the calibration DACs, so they (and I) suggest using the Windows/DOS software to calibrate (about once/year). Requires: National Instuments atMIO16x card and User's Manual. Bj0rn Ekwald's modules package (standard w/ most Linux distributions now) Linux-2.0.0 or later. (it can be compiled in 1.3, but requires a minor change to the IRQ function headers - See Makefile). I haven't tried 2.1 yet. /usr/include/linux/autoconf.h should correctly reflect your kernel configuration. This file is created by "make config" when compiling the kernel. Note: This driver was inspired by the Lab-PC+ device driver written by Glenn Moloney (I highly suggest using this driver to learn how to write Linux device drivers), and available at Installation: ------------- This driver must be compiled into a module before it can be used. Once created, it can be installed, removed, etc., just like other modules, into the kernel during run-time. "make dep" to build the dependancies file "make" to build the driver module "make dev" (as root) to make the special file entries in /dev/atmio16x. These devices are: crwxrwxrwx 1 root root 60, 1 Dec 18 1996 /dev/atmio16x/adc crwxrwxrwx 1 root root 60, 0 Dec 18 1996 /dev/atmio16x/atmio16x crwxrwxrwx 1 root root 60, 2 Dec 18 1996 /dev/atmio16x/dac crwxrwxrwx 1 root root 60, 3 Dec 18 1996 /dev/atmio16x/dio where 60 is the major device #, and 0-3 are the minors. The names do not really matter as long as the major & minor #'s are correct. "chmod gou+rwx /dev/atmio16x /dev/atmio16x/??*" (as root) if you want to allow non-root users to use these devices. "insmod atMIO16x.o" (as root) to load the module into the kernel. "rmmod atMIO16x.o" (as root) to unload the atmio16x module You can also supply the following useful arguments when loading the driver: "insmod atMIO16x.o atmio16x_base=0x2C0 atmio16x_irq=5 atmio16x_dmaa=6 atmio16x_dmab=7" atmio16x_base: the base address of the board - set via jumpers atmio16x_irq : which irq to be used atmio16x_dmaa: the DMA channel to be used by the ADC atmio16x_dmab: the DMA channel to be used by the DAC This command will override the default values of the board base address, interrupt request number and DMA channels. You can also change the device major (atmio16x_major) this way, but be careful to create the /dev/atmio16x/* entries with the correct major number. It is helpful to check ('cat /proc/interrupts', 'cat /proc/ioportrs', and 'cat /proc/dma') to see which IRQ's and DMA's are available. The atMIO16x entries in these proc files should show up after the module is loaded (w/ insmod). The kernel log (accessible via: `tail -f /var/log/messages` or `dmesg`) will have messages from the driver. Demo programs: (test_adc, test_dac, test_dio) -------------- (Note: the driver maintains its state between open/closings, so these command lines are split into multiple invocations, but could just as well be all on one line) test_adc init clear usedma setclock 1000000 setsr 20000 posttrig setcnt 1001 test_adc add 0 start test_adc trigger read jj.pcm will convert 1000 samples and save them in the file 'jj.pcm' test_adc init clear usedma setclock 1000000 setsr 20000 pretrig \ add 0 start trigger read jj.pcm will convert samples continuously, saving into 'jj.pcm' test_dac init setclock 1000000 setsr 20000 start trigger write jj.pcm will convert the contents of jj.pcm test_dio aout bout set 0 pause ain bin get will read the digital lines (after you press return) Usage: ------ The ADCs and DACs can be programmed in a number of different modes. Selection of modes is performed by ioctl() system calls. These ioctl calls are documented below, in the header file (atmio16x.h), and (where else!) in the source code (not in English, but in C :-<). Note that in several places, I used the datatype 'unsigned long long', which is a 64-bit integer that GCC knows about. Two major types of operation include 'pretrig' and 'posttrig' operation. For pretrig operation (the default), collection will only stop when a trigger is generated, after which a specified number of samples will be converted. In posttrig operation, a specified number of samples will be converted once a trigger is received. PRETRIG: Thus, test_adc init clear add 0 setcnt 1 start trigger read jj.pcm will read continuously from channel 0 (at default samplerate of 20000) ad infinitum, until a CTRL-C, or you type test_adc trigger in another window. In this mode, SETCNT sets the number of samples that will be converted AFTER the trigger. Note: SETCNT must be >= 1. If you don't want that last sample, throw it away. Note: the first trigger is necessary to simply initiate conversion. The second trigger stops it. POSTTRIG: test_adc init clear add 0 setcnt 1000 start trigger read jj.pcm will read 1000 samples from channel 0 (at default samplerate). It will then stop automatically. Note: the first trigger is the only one needed. I've never tried hardware triggering, but it should be very simple to add, if it doesn't already work. Programming ADC channel sequence: All input channels must be sampled at the same rate. The total number of channels * the samplerate of each must cleanly divide the clockspeed. You can add 'ghost' channels that do not get included in the multiplexed output of the driver, but which take up space. The ioctl(ADC_ADDCHAN) is used to add each channel, one by one, to the list. ADC_CLEAR clears the list of channels. ADC_CHAN passes a structure (defined in atmio16x.h) which has fields specifying gain, inputmode (NRSE, RSE, DIFF - see Reference manual), whether this channel is a ghost, and bipolar/unipolar conversions. I typically use the device in a continuous running mode. Thus, we select channels, sampling rates, etc., then start converting continuously for hours, days, or weeks, depending on the experiment. The data is read into ring buffers by a multithreaded real-time data acquisition program that can do various things to it, like send it over a pipe or socket to other programs possibly on other machines, detect "important" pieces of data and save them to disk automatically, or triggered by the user, display the data in scrolling windows, or display a scrolling spectrograph/sonograph of the data, etc. The DAC support was also designed based on my needs. We run the DAC continuously, converting zero's by default. We use them to play acoustic signals. What I needed was something that would let me play a signal which was aligned with the data coming in on some ADC channel. The DAC interrupt service routine will write a buffer of zeros if there is no input... What my data collection program does is start the DAC and the ADC simultaneously, and then collect ADC samples. The DAC sends zero's to the output, since I'm not writing anything else. When I wish to play a sound, I start writing it to the DAC, and after I start, I can call ioctl(DAC_GETWRITETIME) to determine the stimulus onset time (in samples, relative to the initiation of conversion). The DAC driver doesn't support features like waveform playing, (repeatedly converting a waveform that's in the FIFO). The DAC can be operated without the ADC, and vice-versa. To trigger ADC conversion w/o DAC conversion, use ioctl(ADC_TRIGGER). To trigger DAC conversion w/o ADC conversion, use ioctl(DAC_TRIGGER). To trigger simultaneous ADC and DAC conversion, use ioctl(DAC_STRIGGER). The Digital I/O code is pretty straightforward. The ioctl interface and the test_dio.c program, along with the AT-MIO-16X Reference Manual, should be sufficient to understand how to use it. Interface Reference: -------------------- For the ADC: ioctl(fd, ADC_STATUS, &status) get value of STAT1 register ioctl(fd, ADC_TRIGGER, NULL) start converting tmp = 1; ioctl(fd, ADC_USEDMA, &tmp) if 1, use DMA, else use IRQ transfers ioctl(fd, ADC_SCONV, NULL) perform single A/D conversion tmp = ADC_TRIGMODE_PRE; ioctl(fd, ADC_SETTRIGMODE, &tmp) ADC_TRIGMODE_PRE: (pretrigger): collect ad infinitum after 1st trigger until 2nd trigger received; then collect n samples ADC_TRIGMODE_POST: (posttrigger): collect n samples after next trigger ioctl(fd, ADC_SETCNT, &n) tell driver how many samples (n) to convert (usage depends on TRIGMODE (see above). n must be 1 <= n <= 65535 (see above) clock = 1000000; ioctl(fd, ADC_SETCLOCK, &clock) set base clock (one of 5000000, 1000000, 100000, 10000, 1000, 100) samplerate = 1000000; ioctl(fd, ADC_SETSR, &samplerate) set samplerate. NOTE that (baseclock % (samplerate * nchans) must equal zero). n = read(fd, databuf, 131072); will never return an amount greater than 1 DMA buffer size, which is always less than 131072. n = select(...) will return when there is data (ie: when read() will return immediately) ioctl(fd, ADC_READFIFO, &sample) read single sample from card's FIFO. ioctl(fd, ADC_INIT, &sample) restores defaults, etc. ioctl(fd, ADC_CLEARCHANS, NULL) clears channel access memory. tmpchan.channel = 0; // 0-7 DIFF, 0-15 SE tmpchan.gain = 1; tmpchan.inputmode = ADC_INPUTMODE_NRSE; // DIFF, RSE, or NRSE tmpchan.ghost = 0; // throw away data tmpchan.unipolar = 0; // unipolar or bipolar ioctl(fd, ADC_ADDCHAN, &tmpchan) add a channel to the channel access memory. ioctl(fd, ADC_STARTACQ, NULL) after finished adding channels. Doesn't actually start converting anything until a trigger is received... ioctl(fd, ADC_STOPACQ, NULL) stops acquisition. bufsize = 32768; ioctl(fd, ADC_SETBUFSIZE, &bufsize) sets the DMA buffer size. (Larger == more latency, but more efficient.) ioctl(fd, ADC_GETBUFSIZE, &bufsize) gets the current DMA buffer size. For the DAC: ioctl(fd, DAC_INIT, NULL); initialize driver ioctl(fd, DAC_SETCHANS, &chan_mask); set the channels to be used. chan_mask is a BITMASK. This card has only channels 0 and 1. To use both, set chan_mask to (1<<0) | (1<<1) == 3. In other words, possible values are: 1,2,3 ioctl(fd, DAC_STATUS, &status); read STAT1 register ioctl(fd, DAC_SETCLOCK, &clockspeed); set base clock (one of 5000000, 1000000, 100000, 10000, 1000, 100) ioctl(fd, DAC_GETCLOCK, &clockspeed); get base clock ioctl(fd, DAC_SETSR, &samplerate); set samplerate. NOTE that (baseclock % samplerate) must = zero). ioctl(fd, DAC_GETSR, &samplerate); get samplerate. ioctl(fd, DAC_START, NULL); enable conversion (but won't ACTUALLY start until DAC_TRIGGER or DAC_STRIGGER is sent) ioctl(fd, DAC_STOP, NULL); stop D/A conversion ioctl(fd, DAC_TRIGGER, NULL); initiate conversions ioctl(fd, DAC_STRIGGER, NULL); initiate ADC and DAC simultaneously. ioctl(fd, DAC_GETWRITETIME, samplecnt); returns the time, in #s of samples since trigger, of the 1st sample of the most recent write(). (actually a little more complex - returns time of 1st sample of most recent write that was preceded by silence)... ioctl(fd, DAC_GETTOTAL, samplecnt); returns the # of converted samples since previous trigger. This is the # of samples PER channel. I.e., if 100000 samples were written to 2 channels (50000 per channel), it will return 50000. write(fd, databuf, total) can break up into smaller pieces and it will still play continuously, provided the delay between subsequent write() calls is not too large. Otherwise, will block until finished. select(...) returns when driver is ready for data (when a write() with a small # of samples can be called without blocking...) For the DIO: mode = DIOMODE_PORTA_INPUT | DIOMODE_PORTB_OUTPUT; ioctl(fd, DIO_SETMODE, &mode); DIOMODE_PORTA_INPUT DIOMODE_PORTA_OUTPUT DIOMODE_PORTB_INPUT DIOMODE_PORTB_OUTPUT logical-OR of these bits will set modes for portA,B. bits = 0xff; ioctl(fd, DIO_SETBITS, &bits); set state of digital I/O bits. ioctl(fd, DIO_GETBITS, &bits); read state of digital I/O bits. Warnings/ToDo/Problems: ADC/DAC: The calibration and EEPROM routines aren't valid. I tried using the procedures for the AT-MIO-16F, but these do not quite work. (write_caldac(), eeprom_shift_*()) ADC: currently, the IRQ routine generates an overflow if there is no empty buffer when it needs one. But between this time and the next call to read(), the card may be happily using it's on-board buffers. What I should do instead is set a defer-dma flag in the IRQ routine, and have the read routine check this, and if the ADC buffer hasn't overflowed, just schedule an interrupt. This works well for the GMAD2A driver, but the AT-MIO-16X card is so slow that this just isn't much of a priority. ADC: I wasn't able to get 32-bit sample counts working. In the case where you only want a specific number of samples, if that # is less than 65536, you can use the ADC_SETCNT ioctl to tell the driver how many samples you want. The main problem was getting Counter 5 working with Counter 4. This code is commented out (in start_adc()). Instead, I'm now using Counter 5 for the DAC. ADC: I wasn't able to use the ADCFIFOREQ bit/feature. By setting this bit, the board is supposed to generate interrupts only when the FIFO is half full (rather than whenever it has a single sample of data). The use of this feature would presumably decrease CPU overhead, and improve throughput. Haven't tried this one in a while, however. Since I use DMA's now, this doesn't make a difference. COUNTER: there is no way to access the 5 programmable counter/timers. Note that most of them are used by ADC and DAC anyway. Nonetheless, there should eventually be a driver for these. ADC/DAC: support mmap() of the DMA buffers, thus avoiding excess copying of data. Given that this is a pretty slow card, this isn't a big deal. ADC/DAC: DMA buffers are allocated even when they are not in use... Fix this. ADC/DAC: I haven't tried interrupt-driven transfers in a long while. This used to work fine, but once I got DMA transfers working, I never looked back. RTSI: there is no mechanism for synchronizing multiple cards via the National Instruments RTSI bus. We only have a single card in any one machine, and these are being used, so this won't be getting supported soon. ADC: I know that single-sample conversion can sometimes lock up the driver. Again, I haven't looked at this in a long time. ADC: I don't ever really use posttrig collection (start collecting after a trigger). It seems to work, though. Certain things are really tailored for pretrig collection: ADC_STOP, for example, generates a trigger, stopping collection. For post-trig, you wouldn't want to call ADC_STOP. DAC: The DAC driver doesn't support features like waveform playing, (repeatedly converting a waveform that's in the FIFO).