



suivant: À propos de ce monter: Annexes précédent: Annexes   Table des matières
The CIO- PDISO16 is a digital I/O board that has 2*8 relay channels and 2*8
digital inputs. It is designed for control and sensing applications where a
few points of high voltage need to be sensed or controlled. The 16 outputs are
electromechanical relays. The contacts are rated at 6A @ 120V A. C. or 28V D.
C., resistive load. The relays are controlled by writing to two 8 -bit ports.
The state of the relays may be determined by a read from the control port address.
There are 16 individual, optically isolated (500V) inputs that may be read back
as two 8- bit bytes. The inputs are not polarity sensitive and may be driven
by either A. C. (50 - 1000 Hz) or D. C. in the range 5V - 24V. Programming is
accomplished by writes and reads to the 8- bit ports. Each bit indicates the
state of an input or controls an output. Pdiso16 is the RTLinux device driver
written by Integrated Real Time Systems (IRTS). The driver has been implemented
as a Linux loadable module for RTLinux 2.2a (2.2.14 Linux kernel). This document
explains the functionalities of the pdiso16 device driver and the programmer's
C-interface library for it.
http://www.computerboards.com www.computerboards.com
The pdiso16 device driver main features are :
Note that the process have to use specialized command codes and ioctl() command constants to communicate with the device driver.
Note that the LSB of the byte is always associated with the lowest channel of the group.
Note that the LSB of the byte is always associated with the lowest channel of the group, the lowest byte is associated with PORT1 and the higher with PORT2 (!).
Note that the LSB of the byte is always associated with the lowest channel of the group, the lowest byte is associated with PORT1 and the higher with PORT2 (!).
Linux kernel modules are specially made to be pieces of kernel that can be loaded and unloaded dynamically, while the kernel is running. These appear as object files (modname.o) and are loaded with the command insmod modname.o [arguments]. This operation runs the initialization of the device(s) and gives a major number to the device driver. This one can be found in the /proc/devices file. The command can receive many arguments specific to the module.
After being loaded, the device driver module must be associated with devices files which will be used by user programs. This is made with the command : mknod devname c major minor. This action can be avoided, if this file is already created with its proper major and minor numbers.
An optional parameters can be specified when you load the pdiso16 device driver with the insmod command. The loading command will looks like (run being root):
insmod pdiso16.o [PDISO16_base_adr=adr | PDISO16_major=major]
The device driver has to register itself with Linux and RTLinux. The loading process begins with a call to pdiso16_init() function dedicated to general purpose initialization (Linux I/O region checking, register device...). Then, it has to deal with RTLinux stuff: register using posixio API, set up fifo's and initialize its base address and communication state (PORT1 byte-wise behavior)
Error can appear during the module loading. This error may be caused by invalid loading parameters or by the fact that module is already running on system.
In case of error, you should check if the module is not already running and if all the required resources are free (see the Special files section). You can also use the dmesg command to see debug or error messages (see the Debug options section).
As you can dynamically load your kernel module, you can also unload it when you want using the command rmmod modname. You can check its name calling the useful lsmod command.
Nevertheless, the module will be unloaded only if all the processes/threads have been closed before. This is done with the driver's release() function call which is called by the generic close() function. Finally, the call may look like close(FD). If some processes/threads are still using the device driver when you try to unload it, the kernel will display a 'busy device or resource' message on console.
Before making an ioctl() call to a special file (device driver description file in our case), the device must have been opened by the RTLinux task, using the driver's open() function call which may look like fd = open("/dev/pdiso", O_NONBLOCK).
Then to make any ioctl() call user has to indicate the file descriptor (int FD) that has been returned by the open() function, a command parameter (unsigned char cmd) and, if required, an argument parameter. The call then may look like err=ioctl(FD, cmd, arg) where err is an integer returned by the function. In the pdiso16 device driver, arg is required while reading digital inputs thus, the ioctl() function waits for well sized buffer . User must then provide a buffer address to the function. Used with CHANGE_PORT command, arg takes two values: PORT1 or PORT2 constants.
This section explains the specifications of cmd and arg parameters and the returned values of the ioctl() function.
This unsigned char parameter is used to indicate to the driver which port(s) (CHANGE_PORT, BOTH_PORTS) you want to read or to write on the pdiso16 board. You can also indicate that you want to read digital inputs of your board (DIGITAL_INPUT).
/*ioctl() cmd constants*/
#define CHANGE_PORT 0xA1
#define BOTH_PORTS 0xA2
#define DIGITAL_INPUT 0xA3
The arg parameter is used, on the one hand, to select a port when theCHANGE_PORT command is used, on the other hand, to get back the value of the digital input channels.
In com.h header file:
/*ioctl() arg constants*/
#define PORT1 0
#define PORT2 1
Example: want to read port2 relay state ?
I decided to use an ioctl() call to read these I/O ports instead of adding another device. Although it can be done easily... The command used is DIGITAL_INPUT to signal the driver it has to read its digital input channels and to get back the value in the result buffer.
Note that the result buffer must be sized according to the number of port you want to read (byte-wise for an only port reading, word-wise for a dual port reading).
Example: want to read port1 then both ports digital input state ?
The ioctl() call returns 0 on success or -1 on fail. In case of fail, errno values are standardized by the include file <asm/errno.h> so that you can know what kind of problem has occurred.
If the driver has the required debug level, you can also use the command dmesg to see in details where and why the ioctl() call has failed.
Writing the pdiso16 digital I/O board means to change the state of the relay channels, ie: to change the connection between pins of the selected relay.
Before making a write() call to a special file (device driver description file in our case), the device must have been opened by the RTLinux task, using the driver's open() function call which may look like fd = open("/dev/pdiso", O_NONBLOCK).
Then to make any write() call user has to indicate the file descriptor (int fd) that has been returned by the open() function, a command buffer and, its size. The call then may look like nbwr=write(fd, &buffer, sizeof(buffer)) where nbwr is an integer returned by the function: the number of bytes written. In the pdiso16 device driver, user can give a byte-wise or word-wise buffer according to his choice of a single port reading or a dual one.
When a 1 is written to the output, the common and the NO ( Normally Open ) pins of the relay, are in contact. User can switch a relay state setting a 1 or 0 to the right place within the byte or word wise buffer. Then the buffer is written down to the board's register by the device driver's write() call...
Reading the pdiso16 digital I/O board means to control the status of the relay channels.
As for an ioctl() or a write() call, before making a read() call, the device must have been opened.
Then to make any read() call user has to indicate the file descriptor (int fd) that has been returned by the open() function, a result buffer and, its size. The call then may look like nbrd=read(fd, &buffer, sizeof(buffer)) where nbrd is the number of bytes read. In the pdiso16 device driver, user can give a byte-wise or word-wise buffer according to his choice of a single port reading or a dual one.
RT-FIFO implements message passing through rt-kernel space... This kind of communication makes me think of QNX's, except that it is non blocking ( which is a pretty big difference ! ). Another difference is that rt-fifos are uni-directional. User must exchange data through a pair of rt-fifos for bi-directional communication. My device driver requires 2 rt-fifos...
typedef struct {
u8 what;
u16 value;
} Message_struct;
#define MESSAGE_SIZE sizeof(Message_struct)
#define WRITE_RELAY 0x1 /*= read() function */
#define READ_STATE 0x2 /*= write() function */
#define TAKE_DATA 0x3 /*= ioctl(fd, DIGITAL_INPUT, ...) function */
AND the ioctl() commands CHANGE_PORT(PORT1/PORT2 in value tag) and BOTH_PORTS.
To receive a message, the Linux user space process must listen to the RTLinux pdiso16 device driver. User can simply implement it with a read() call or use read() with a select() call on the rt-fifo. Example could be fount in the app.c source file. A Message_struct data structure must be used while calling the read() function.
The device driver passes on an acknowledgment message to the Linux process when no result is required ( write or ioctl commands ). Thus, the device driver says that it has received the command ant that it has succeeded ; the Message_struct's what member contains: RECV_DONE.
#define RECV_DONE 0x10
Error codes are sent to the Linux process the same way.
/* Error codes in com.h */
#define RECV_ERR1 -1
#define RECV_ERR2 -2
#define CMD_ERR -3
#define SEND_ERR -4
The result message's what member is always TAKE_DATA and its value member is filled with the result of the request previously passed on to the device driver. If a single port access is done, this 16 bits value only contains its lowest byte valid. I recommend to give a 0 filled value member to the read() function. In case of a double port access, the entire value field is OK and the lowest byte corresponds with port1.
At the compilation of the driver, user can specify which level of debug he wants to be displayed on the kernel log. This is done by uncommenting #define DEBUG for debug level 1 or both #define DEBUG and #define DEBUG2 for debug level 2 in the pdiso16.c file. In real-time In general, debug level 1 displays actions and probable causes of command faults and debug level 2 add the state of important global and local variables at that time so that you can determine what was wrong.
You can also display all the kernel messages by using dmesg command.
Note that debug options slow down the device driver.
Kernel uses special files to save all the systems parameters. Some of those can be very useful to get informations about the device driver :