The Heber hardware supports parallel inputs, DIP switches and parallel outputs. The driver offers mechanisms to access these inputs and outputs.
There are two basic mechanisms to access the DIP switches:
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.
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 ;
}
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.
Read the value of switch 10:
#cat /proc/axis_00/dipswitches/10 0
To read all the switches read the all file.
#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.
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 ;
}
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
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).
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
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.
#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.