Main Page | Index

Axis Software User Manual


Parallel I/O

The Heber hardware supports parallel inputs, DIP switches and parallel outputs. The driver offers mechanisms to access these inputs and outputs.

DIP Switches

There are two basic mechanisms to access the DIP switches:

Accessing DIP Switches through Char Dev Interface

To access the DIP switches through the character device interface, a suitable device node special file needs creating. This is a one off operation part of the task of creating an initial file system. The conventional place to put this is in the /dev directory. For convenience the example will be created in the /dev/axis_0 directory.

This command needs root privileges:

#mknod -m666 /dev/axis_0/dipswitches c 231 32

The 231 number is called the device major and identifies the Axis device driver. The second number 32 is used by the Axis driver to identify that the caller is interested in the DIP switches.

Assuming that the Axis module has been loaded (and the existence of a hexdump command) then the DIP switches can be checked with the command:

# cat /dev/axis_0/dipswitches |hexdump -C

This should return:

00000000	01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 01 |...............|
00000010

Reading this file should return 16 bytes, one byte per switch. If the switch is closed the byte will be 01. If the switch is open the byte will be 0. This interface is also accessible to programs written in C and most other programming languages.

Example

A simple program to show the state of DIP switches 8 and 9.

#include <stdio.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

char * device_name = "/dev/axis_0/dipswitches" ;

int main(int argc ,char ** argv)
{
	int dipswitches ;
	unsigned char switch_8 ;
	unsigned char switch_9 ;
	dipswitches=open(device_name,O_RDONLY) ;
	if (!dipswitches)
		{
		fprintf(stderr,"Failed to open %\n",device_name);
		exit(1) ;
		}
	// Now read switches 8and 9

	lseek(dipswitches,8,SEEK_SET);
	read(dipswitches,&switch_8,sizeof(switch_8));
	read(dipswitches,&switch_9,sizeof(switch_9));
	printf("switch 8 is %s\n",switch_8?"ON":"OFF");
	printf("switch 9 is %s\n",switch_9?"ON":"OFF");
	close(dipswitches) ;

return 0 ;
}

Accessing DIP Switches through /proc Interface

The Axis driver also provides an alternate mechanism for reading the DIP switches via the /proc interface. The driver creates a tree of /proc file system entries. All the DIP switch entries are to be found in the /proc/axis_00/dipswitches directory. This has seventeen entries one for each DIP switch and one for all switches together. To read an individual switch read the /proc entry for it.

Example

Read the value of switch 10:

#cat /proc/axis_00/dipswitches/10
0

To read all the switches read the all file.

Example

#cat /proc/axis_00/dipswitches/all
1000001111001111

Note: the /dev interface returns a binary value indicating the state of a switch. The /proc/axis_00/dipswitches entries all return text representations of the current state. So if a switch is off the /proc returns ascii 0 not binary zero.

Accessing Inputs through Char Dev Interface

The inputs follow the same model used for the DIP switches. The character device minor number changes to 16, so the command to create a device node is:

#mknod -m666 /dev/axis_0/inputs c 231 16

There are 40 inputs (32 general purpose and 8 reporting status values) so the returned string is longer.

#cat /dev/axis_0/inputs |hexdump -C
00000000	00	00	00	00	00	00	00	00	00
00	00	00	00	00	00	00	|................|
*
00000020	01	01	01	01	01	01	01	01	|........|
00000028

The code to read the inputs is just like the code to read the DIP switches:

#include <stdio.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

char * device_name = "/dev/axis_0/inputs" ;
int main(int argc ,char ** argv)
{
	int inputs ;
	unsigned char input_8 ;
	unsigned char input_9 ;
	inputs=open(device_name,O_RDONLY) ;
	if (!inputs)
		{
		fprintf(stderr,"Failed to open %\n",device_name);
		exit(1) ;
		}
	// Now read switches 8and 9

	lseek(inputs,8,SEEK_SET);
	read(inputs,&input_8,sizeof(input_8));
	read(inputs,&input_9,sizeof(input_9));
	printf("input 8 is %s\n",input_8?"ON":"OFF");
	printf("input 9 is %s\n",input_9?"ON":"OFF");
	close(inputs) ;

return 0 ;
}

Accessing Inputs through /proc Interface

As with the DIP switches the driver supports an alternative text based /proc interface. The driver creates a /proc/axis_00/inputs directory and then fills it with entries. Again the all entry returns a text string representing all the inputs states concatenated. The 32 numeric values return the state of current general purpose input associated with that number. The status values are assigned names and reading the file will return the current state.

#cat /proc/axis_00/inputs/0
0

#cat /proc/axis_00/inputs/meter_sense
1

Accessing Outputs through Char Dev Interface

It will come as little surprise that the outputs can be read in the same way. A device node with minor number 48 is needed.

#mknod -m666 /dev/axis_0/outputs c 231 48

Then the command:

#cat /dev/axis_0/output |hexdump -C
00000000	00	00	00	00	00	00	00	00
00	00	00	00	00	00	00	00 |................|
*
00000020

Code to read the outputs is just the same as the code to read the inputs and DIP switches. Read only outputs are useless, so to take advantage of the outputs you will need to write to them.

The outputs character device behaves as a file the first byte equals the first output, the second byte the second output. Non zero values turn the output on.

#include <stdio.h>

#include <unistd.h>
#include <sys>
#include <sys>
#include <fcntl.h>

char * device_name = "/dev/axis_0/outputs" ;
int main(int argc,char ** argv)
{
	int outputs ;
	int x,y ;
	unsigned char output_map[32]={0};
	outputs = open(device_name,O_RDWR);
	if (!outputs)
		{
		fprintf(stderr,"open failed\n");
		exit (-1) ;
		}
	for (y=0;y<10;y++)
		{
		for (x=0;x<32;x++)
			{
			lseek(outputs,0,SEEK_SET);
			output_map[x]=1;
			write(outputs,output_map,sizeof(output_map));
			output_map[x]=0;
			usleep(10000);
			}
		}
	memset(output_map,0,sizeof(output_map));
	lseek(outputs,0,SEEK_SET);
	write(outputs,output_map,sizeof(output_map));
	close (outputs);
	return 0 ;
};

The driver ensures that writes are atomic, in practice this allows multiple programs / threads to concurrently modify the outputs and as long as each program only modifies a unique set of non intersecting outputs the system will be proof against race conditions. To modify just a small set of outputs use lseek to position the file pointer to the first output of interest and only write the outputs of interest.

Code to change output 10 might look like:

unsigned char state= 1 ;

lseek(outputs,10,SEEK_SET);
write(outputs,&state,sizeof(state));

In this case other applications can safely modify all other outputs. If this seems clumsy then the lseek and write can be combined using the pwrite call.

unsigned char state= 1 ;

pwrite(outputs,&state,sizeof(state),10);

This may possibly be slightly faster as it involves only one system call (pread and pwrite are kernel system calls not library calls).

Accessing Outputs through /proc Interface

The driver also provides a text based /proc interface to the outputs. In the /proc/axis_00/outputs directory the driver creates a set of entries numbered 0 -31 and one called all.

To read an output:

#cat /proc/axis_00/outputs/0
0

The interface also allows outputs to be written to. #echo -n 1 > /proc/axis_00/outputs/0 will turn on output 0. Note the -n option is needed to suppress any trailing newline characters appended by the echo command. Reading the all entry returns at string with a concatenation of the current output state.

Writing a string to all will attempt to interpret the input string, the first byte matching the first output. Given this text based interface even simple shell scripts can animate the outputs.

For example, try:

#/bin/bash

let count=0
let dir=0

while [ "1" -eq "1" ]

do
    zero="00000000000000000000000000000000"
    for i in `seq 0 32`
    do
        if [ "$count" -eq "$i" ]
        then

             output="${zero:1:${count}}1${zero:${count}:27-${count}}"
        fi
    done
    #echo -n $zero > /proc/axis_00/outputs/all
    echo -n $output > /proc/axis_00/outputs/all
    #echo $output

    if [ $dir -eq 0 ] ; then
        let count+=1
        if [ $count -eq 27 ] ; then
            let dir=1
        fi

    else
        let count-=1
        if [ $count -eq 0 ] ; then
            let dir=0
        fi
    fi

    usleep 10000
done

Asynchronous Input Handling

The hardware handling the 32 parallel inputs has the facility to detect input changes and then issue an interrupt request. This allows an alternative input model, rather than the application continuously polling to read the current state of the inputs, it can allow the driver to signal changes.

Linux supports the concept of signals. These are rather like interrupts, the application can register a function to be called using the signal call or preferably the sigaction call. When a signal condition occurs the kernel will call the registered callback function. The execution of this callback is completely asynchronous with respect to the currently running application so signals need careful handling as they can introduce races and cause problems with the C run time library. Used carefully than can offer a worthwhile optimisation.

The kernel defines signals for many conditions not all can captured by an application, SIGKILL (-9) for instance is acted on by the kernel and the application is killed rather than signalled.

The Axis driver if requested will raise a SIGIO if it detects a change to one of the inputs. This is one signal shared for all I/O so the application may still have to take measures to detect the source of the signal.

To request asynchronous I/O notification an application needs to open the device request ownership and then set the files FASYNC flag.

unsigned long flags ;
int handle ;
....

handle = open("/dev/axis_0/inputs",O_RDONLY);
if (handle)
	{
	fcntl(handle,F_SETOWN,getpid());
	flags=fcntl(handle,F_GETFL);
	fcntl(handle,F_SETFL,flags|FASYNC);
	etc

A simple if not terribly safe sample program might look like:

#include <STDIO.H>
#include <SIGNAL.H>
#include <SYS/TYPES.H>
#include <SYS/STAT.H>
#include <FCNTL.H>
#include <UNISTD.H>

int handle ;
unsigned char buffer[32];
static void input_handler(int sig_number)
{
	int x ;
	if (sig_number==SIGIO)
		{
		pread(handle,buffer,32,0);
		for (x=0;x<32;x++)
			{
			printf("%d ",buffer[x]) ;	//printf is probably not safe to
			 				call from inside a signal
			}
		printf("\n");
		}
}

int main(int argc ,char ** argv)
{
	unsigned long flags ;
	struct sigaction input_change_handler_signal ;
	memset(&input_change_handler_signal,0,sizeof(input_change_handler_signal));
	input_change_handler_signal.sa_handler = input_handler ;
	sigaction(SIGIO,&input_change_handler_signal,NULL);
	handle = open("/dev/axis_0/inputs",O_RDONLY);
	if (handle)
		{
		fcntl(handle,F_SETOWN,getpid());
		flags=fcntl(handle,F_GETFL);
		fcntl(handle,F_SETFL,flags|FASYNC);
		while (1)
			{
			usleep(100000);
			//don't call printf here to avoid races
			}
		}
	close(handle);
	return 0 ;
}

As noted in the comments, the printf calls in the kernel are a possible source of race problems. Linux offers an alternative mechanism for handling signals the sigaction call allows SIG_IGN to be defined as a handler will ignores the signal. An application can then use the sigwait call to wait for a signal. Waiting for a signal is not something the main loop of an application would want to do, but its the sort of thing a thread could handle.

Example

#include <SIGNAL.H>
#include <SYS/TYPES.H>
#include <SYS/STAT.H>
#include <FCNTL.H>
#include <UNISTD.H>
#include <PTHREAD.H>

static sigset_t signal_set ;
unsigned char buffer[32];
volatile int exit_signal = 0 ;

static void * input_thread(void * arg)
{
int x ;
int signal_number ;
int handle ;
unsigned long flags ;
handle = open("/dev/axis_0/inputs",O_RDONLY);
if (handle)
	{
	fcntl(handle,F_SETOWN,getpid());  //transfer ownership
	flags=fcntl(handle,F_GETFL);  //get control flags
	fcntl(handle,F_SETFL,flags|FASYNC); //add FASYNC
	while(1)
		{
		sigwait(&signal_set,&signal_number);  //wait for signal
		if (signal_number==SIGIO)  //need to handle different signals differently
			{
			pread(handle,buffer,32,0);
			for (x=0;x<32;x++)
				{
				printf("%d ",buffer[x]) ;  //clib now protected by
								pthreads library
				}
			printf("\n");
			}
		else
			{
			break ;  //default here for unknown signals is to exit the
					application cleanly
			}
		}
	}
close(handle);
exit_signal = 1 ;  //Let the main loop know its all over
}


static void setup_thread(void )
{
	struct sigaction action ;
	pthread_t thread ;
	sigfillset(&signal_set);
	memset(&action,0,sizeof(action));
	action.sa_handler = SIG_IGN ;
	sigaction (SIGIO,&action,NULL);
	sigaddset(&signal_set,SIGIO);
	pthread_sigmask(SIG_SETMASK,&signal_set,NULL);
	pthread_create(&thread,NULL,input_thread,&signal_set);
}


int main(int argc ,char ** argv)
{
	setup_thread();
	while (!exit_signal)
		{
		usleep(1000);
		}
	return 0 ;
}

This approach is better but it should be noted that not all signals can be handled this way. Illegal instructions and invalid memory references can all generate signals, but these will be associated with the thread that caused them rather than the handling thread that is defined here.

In conclusion, the Axis inputs offer the facility to use signals to generate notifications of input changes. Linux signal handling can be complex and introduce race conditions but is slightly more efficent. The use of signals for Axis I/O is completely optional. Polling the inputs takes less than 5 microseconds and is simple safe and straightforward.


© HEBER LTD. 2005. This document and the information contained therein is the intellectual property of Heber Ltd. and must not be disclosed to a third party without consent. Copies may be made only if they are in full and unmodified. The information contained in this documentation is believed to be accurate and reliable. However, Heber Ltd. assumes no responsibility for its use, and reserves the right to revise the documentation without notice.
Document No: 80-17794, Issue 4r1    Release Date: 01.12.05     Email: support@heber.co.uk    www.heber.co.uk