The Opto driver exposes a fairly simple interface to the userland applications which can be used to set various hardware specific parameters at runtime. It can, for example, be used to intialize the interrupt controller to a different mode, to load one of the counters with different start value, or to read and write device registers.
The interface is made for applications written in C and C++ languages. It can probably not be used by any other programming language without modification. It consists of a single header file opto.h. Libraries or similar things are not needed.
The interface has a number of functions you can call from within your program. These finctions are implemented as inline calls to ioctl(2); in C they are macros, in C++ they are inline functions. It also has a few data types, which are implemented as a C structs. For a list of available finctions and data types, see the reference.
You can work with the /dev/opto0 device just like you would with any other device in the system. You open it, read data from it and close it when finished. Although you can write to this device, you should not, because write support is usable only for testing purposes.
The device can be opened using open(2) system call. The equivalent from the library of your favourite programming language will work too (for example, the fopen(3S) call from the C library). You can pass any of open parameters to the driver. For example:
int fd; fd = open("/dev/opto0", O_RDONLY);
Note that if you use a flag that enables you write access, the driver will ignore the creation and positional flags. This means, when usiong the open(2) system call, the driver will ignore the flags O_APPEND and O_CREAT. This is obvious, because the device cannot be created if it doesn't exist and since you write to the circular queue, the data is streamed and not geographic.
When reading from the device, you use a normal read call, for example. The driver will return bytes from the circular queue, up to the content of the circular queue. When the queue is empty, the driver will report succeeded read operation, but will give you zero bytes. Note that you cannot read the contents of the input registers without using a ioctl(2) call. You can only read the contents of the circular queue.
You can use the opto_pic_init function to intialize the on-board interrupt controller. Similarily, the function opto_cnt_init can be used for initializing on-board counters. Here is an example the illustrates the initialization of the interrupt controller:
#include <opto.h> void initialize_pic(void) { int ret; int fd; unsigned char picm; /* Open the device */ fd = open("/dev/opto0", O_RDWR); if (fd == 0) return; /* Initialize PIC to flank mode */ picm = OPTO_PIC_FLANK; opto_pic_init(fd, &picm); /* Close the device and exit */ close(fd); return; }
Another example illustrates how to initialize the counters. In this example, the first two counters are set up to use the operatinng mode 3, whie the third counter is set up for the operating mode 2.
#include <opto.h> void initialize_counters(void) { int ret; int fd; opto_cnt_t ci; /* Open the device */ fd = open("/dev/opto0", O_RDWR); if (fd == 0) return; /* Initialize first two counters to produce square pulses */ ci.mode = OPTO_CNT_SQUAREGEN; ci.num = 0; ci.stval = 0x4; opto_cnt_init(fd, &ci); ci.num = 1; ci.stval = 0x14; opto_cnt_init(fd, &ci); /* Initialize the third counter to divisor-by-n mode */ ci.mode = OPTO_CNT_NDIV; ci.stval = 0xa; ci.num = 2; opto_cnt_init(fd, &ci); /* Close the device and exit */ close(fd); return; }
The previous examples use the default start values for the counters. The initial state of the hardware after the driver has been loaded, is just as if these examples have initialized it.
The internal interrupt mask can be set at any time. The hardware has eight internal interrupt channels and each of them can be masked regardless of the others. You can retrieve the current interrupt mask by calling the function opto_get_intrmask and set a new mask by using the opto_set_intrmask function.
Here is a small example in which we read the current interrupt mask, invert it, and set the inverted mask:
#include <opto.h> void invert_interrupt_mask(void) { int ret; int fd; unsigned char im; /* Open the device */ fd = open("/dev/opto0", O_RDWR); if (fd == 0) return; /* Invert the interrupt mask */ opto_get_intrmask(fd, &im); im = ~im; opto_set_intrmask(fd, &im); /* Close the device and exit */ close(fd); return; }
Registers can be read from and written to at any time, However you must be careful and be sure to know what you are doing. In general, reading a register won't hurt anyone, but sometimes writing a register requires additional action, otherwise the hardware might get confused.
To read the value of a register, call opto_read_reg, to write to a register call opto_write_reg. The following example shows how to read the state of the input registers:
#include <opto.h> int read_counter_state( unsigned char* in0, unsigned char* in1) { int ret; int fd; opto_reg_t ri; /* Open the device */ fd = open("/dev/opto0", O_RDWR); if (fd == 0) return (-1); /* Read the input states */ ri.reg = OPTOIN16_REG_IN0; opto_read_reg(fd, &ri); *in0 = ri.val; ri.reg = OPTOIN16_REG_IN1; opto_read_reg(fd, &ri); *in1 = ri.val; /* Close the device and exit */ close(fd); return 0; }