[comp.sys.hp] Hpib

zaka@icarus.eng.ohio-state.edu (Zaka U. Bhatti) (10/27/90)

I not sure to post this in this news group but here it goes.

We have an HP 9000(300 series) machine that we want to use to
control instruments like spectrum analyzer, function generators etc.
I don't why but ordered Hp basic software to write program to control
these instruments.

i know we can write software in C for GPIB. But are there libraries and
stuff to write the programs in C for HPib. Are there companies that
sell packages to write these control progams in C language. Better
yet are there public domain software that do this.

I am asking this because we can very easily write software in C for 
GPib but are having a rough time to do it for HPib in HP basic. 
 
Appreciate any help on this subject. 
Please respond by mail to zaka@icarus.eng.ohio-state.edu

zaka-=-

edo@hpislx.HP.COM (Ed Overacker) (10/29/90)

Here is a copy of an article for I/O programming -- taken after RMB concepts.
I am only posting it, I did not write it so please don't call.

Ed Overacker

 -----------------------------------------------------------------------------

Device and Peripheral I/O with Series 300 HP-UX

Rocky Mountain BASIC programmers love to do IO, but claim HP-UX is too
unwieldy and unfriendly to get their job done.  From their viewpoint (and
that of many others), device IO under UNIX is nearly impossible.  Under
HP-UX, it can be done with relative ease--relative to UNIX, that is.
If you know all the pieces of the puzzle, and have some examples to
start from, you can be talking to instruments and other peripherals
"real soon now".  This article is somewhat of a cookbook approach to 
device IO under HP-UX.

The tack I'm going to follow is to examine a few simple programs written in
Rocky Mountain BASIC (RMB).  The programs perform several operations that are
trivial to perform in RMB, but take both System Administrator and programmer
effort under HP-UX.  The "BASIC" operations to be performed are:

1. Scanning the backplane to determine which cards are installed; this will be
   done with a READIO-like operation to make a memory-mapped read to the
   registers on the cards.
2. Provide an analog to the ASSIGN statement which selects the driver and
   performs some error checking on parameters.
3. Provide ENTER/OUTPUT statements that can be used with an ASSIGNed "IO path";
   include simple IMAGE (formatting) support.

<Header>  Reading the Backplane Configuration

Most "intelligent" I/O applications I have seen or written have a section
which programmatically identifies the available I/O cards.  This allows a
program to determine which select codes to use, or to gracefully terminate
if required cards are missing.  The following BASIC program does this.

10    !  THIS  PROGRAM REPORTS ON THE IO CARDS INSTALLED IN THE
20    !  BACKPLANE BY READING DIO REGISTER 1.
30    !
40    INTEGER Select_code,Id
50    DIM Card$[80]
60    Select_code=8  !  INTERNAL HPIB DOES NOT FOLLOW DIO CONVENTION
70    REPEAT
80      ON ERROR GOTO No_card
90      Id=READIO(Select_code,1)  !  BYTE-WIDE
100     OFF ERROR 
110     SELECT Id
120     CASE 1
130       Card$="HPIB "
140     CASE 2
150       Card$="DUMB RS232, NON-REMOTE"
160     CASE 3
170       Card$="GPIO"
180     CASE 4
190       Card$="BCD"
200     CASE 5
210       Card$="MUX"
220     CASE 8
230       Card$="FAST HPIB"
240     CASE 10
250       Card$="FLOATING POINT CARD"
260     CASE 15
270       Card$="CUSTOMER CARD, ID=15"
280     CASE 16
290       Card$="CUSTOMER CARD, ID=16"
300     CASE 17
310       Card$="VME ADAPTER"
320     CASE 18
330       Card$="ANALOG TO DIGITAL"
340     CASE 21
350       Card$="LAN INTERFACE"
360     CASE 27
370       Card$="EPROM PROGRAMMER"
380     CASE 28
390       Card$="RGB MONITOR"
400       Select_code=Select_code+1     !  RGB CARDS SPAN TWO SELECT CODES
410     CASE 30
420       Card$="BUBBLE MEMORY"
430     CASE 52
440       Card$="DATACOMM OR SRM, NON-REMOTE"
450     CASE 66
460       Card$="DUMB RS232, NON-REMOTE"
470     CASE 130
480       Card$="DUMB RS232, REMOTE"
490     CASE 180
500       Card$="DATACOMM OR SRM, REMOTE"
510     CASE ELSE
520       Card$="UNDEFINED ID: "&VAL$(Id)
530     END SELECT
540 Print_it:PRINT Select_code;TAB(20);Card$
550     Select_code=Select_code+1
560   UNTIL Select_code>31
570   STOP
580 No_card:Card$="NO CARD INSTALLED"
590   GOTO Print_it
600   END

In the following discussions, a number preceded by 0x is in hexadecimal.
DIO cards are memory mapped into addresses 0x600000 (select code 0) through
0x7F0000 (select code 31), 0x10000 (decimal 65536) bytes per card.  Register
numbers are byte offsets from the base address of a given select code.
By DIO definition, register 1 (byte wide) on a card contains the primary
ID of the card.  The READIO statement in line 90 reads this register.
In BASIC select codes 0-6 are not available for DIO card use, and select
code 7 is reserved for the internal HPIB.  The ON/OFF error surrounding
the READIO traps bus errors caused by unused (no card at) select codes.

Here's a quick primer to help BASIC programmers understand the the C version
of the preceding code:

1. Variables (local and external) and external procedures must be declared
   before use.  The type binding is somewhat loose (for example, freely
   mixing integers and characters).
2. All procedures are really functions which always return a value (which may
   be ignored).  Conventionally, <0 means error/failure; =0 means success;
   >0 means other information associated with success.
3. All parameters are pass by value (unlike BASIC's pass by reference).  To
   effect a pass by reference, pass the ADDRESS of the variable (with the "&"
   operator).  When given an address, a variable is dereferenced with the "*"
   operator.
4. "Strings" are actually packed arrays of characters, terminated by the
   null character (ASCII 0).  Library routines are available for determining
   length, performing concatenation, etc.  

#include <stdio.h>
#include <errno.h>

main()		/* Print summary of all IO cards in backplane */
{
	int select_code, id;	/* Similar to the BASIC declarations */
	char card[80];		/* Strings are done with character arrays */

	extern int readio();	/* A separate code module */
	
	extern int errno;		/* The last error number */
	extern char *sys_errlist[];	/* An array of "string pointers" */

	for (select_code = 0; select_code <32; select_code++) {
		
		if (select_code == 7) 	/* For all supported configurations */
			id = 1;
		else 
			id=readio(select_code,1);   /* Modeled after BASIC */

		switch (id) {
			case -2: strcpy(card,"no card"); break;
			case -1: strcpy(card,"bad readio: ");
				 strcat(card,sys_errlist[errno]);
				 break;
			case  1: strcpy(card,"hpib"); break;
			case  2: strcpy(card,"dumb rs232, non-remote"); break;
			case  3: strcpy(card,"gpio"); break;
			case  4: strcpy(card,"bcd"); break;
			case  5: strcpy(card,"mux"); break;
			case  8: strcpy(card,"fast hpib"); break;
			case 10: strcpy(card,"floating point card"); break;
			case 15: strcpy(card,"customer card, id=15"); break;
			case 16: strcpy(card,"customer card, id=16"); break;
			case 17: strcpy(card,"vme adapter"); break;
			case 18: strcpy(card,"analog to digital"); break;
			case 21: strcpy(card,"lan"); break;
			case 27: strcpy(card,"eprom programmer"); break;
			case 28: strcpy(card,"rgb monitor"); 
				 select_code += 1;
				 break;
			case 30: strcpy(card,"bubble memory"); break;
			case 52: strcpy(card,"dcomm/srm, non-remote"); break;
			case 66: strcpy(card,"dumb rs232, non-remote"); break;
			case 130: strcpy(card,"dumb rs232, remote"); break;
			case 180: strcpy(card,"dcomm/srm, remote"); break;
			default: sprintf(card,"unknown id %d",id); break;
		}
		printf("%d %s\n",select_code,card);
	}
	exit(0);
}

A single user, single task OS like BASIC is quite lax about access to system
resources (such as IO cards).  A multitasking, multiuser operating system like
HP-UX restricts access to its resources.  Also, in HP-UX all access to
peripherals is done through the file system via "special files" or
"device files".  Only the system administrator (SA) can use the mknod(1m) 
command to set up device files.  The owner/group/other permissions of the HP-UX
file system will then determine the fashion in which "permitted" users can 
access the device files.

The mknod command needs driver information (software) and select code
information (hardware) to create the device file.  Memory mapped access to 
DIO cards requires the iomap(4) driver
to map the cards into user address space.  The following mknod(1M) command
will create the necessary device file to perform memory mapped access to a
DIO card at select code 12 (remember, you must be superuser):

# mknod /dev/iomap_sc_12 c 10 0x006c01
# ll /dev/iomap_sc_12
crw-rw-rw-   1 root     other     10 0x006c01 Jun  2 11:16 /dev/iomap_sc_12

Refer to section 1M of the reference manual for a generic description of mknod.
All memory mapped files must be character ("c") special files.
The major number (driver) must be (decimal) 10; the minor number is composed 
of two parts.  The upper
four (hex) digits are the base address of the region to be mapped, divided
by 0x10000 (i.e., shifted right 4 hex digits).  For a card at select code 12, 
the base address is

0x00600000 + (decimal 12 * 0x10000) = 0x006c0000.

The division yields the 4 hex digits 0x006c.  Note that this scheme will allow
any region in the entire 32-bit address space to be mapped.
The lower two hex digits of the
minor number are the number of 64k (0x10000) byte blocks to map in; since
each IO card spans this much space, it should be set to one.  
The SA should consider the choice of file access permissions according to the
use of the IO card and the users who might be able to access it.

How do I use this file from my user program?  By making a call to the driver,
HP-UX will map the physical address space into my user space, allowing me
to access the card as a program variable.  Make sure you know what reading
and writing to registers on the card does to the card!!!

The following code assumes there is a device file in /dev for each of the
select codes 0-31.  The file names should be of the form
iomap_sc_xx, 0 <= xx <= 31.  The minor number for each file should be 
constructed according to the preceding rule.  

#include <fcntl.h>
#include <sys/iomap.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <setjmp.h>

int read_ok;   /* needs to be global for access by error handler */
jmp_buf env;   /* TRUST ME */

int readio(sc,reg)		/* success returns 0-255 */
int sc,reg;
{
	char devfilename[80],		/* special character file */
	     *shmem,			/* for shared memory */
	     *dio_space;		/* byte-wide memory access */
	int  iomap_fd,			/* file descriptor */
	     no_card();			/* bus error handler */

	if (sc<0 || sc>31 || reg<0 || reg>0xffff) return(-1); /* range check */

	sprintf(devfilename,"/dev/iomap_sc_%2.2d",sc); /* form the file name */

	if ((iomap_fd = open(devfilename,O_RDONLY)) < 0) return(-1);
		
	shmem = (char *) 0; 		/* attach at system default address */
	if (ioctl(iomap_fd,IOMAPMAP,&shmem) < 0) return(-1);
			
	dio_space = shmem;		/* save the attach point */
	dio_space += reg;               /* offset by reg */

	read_ok = 1;			/* set the global */
	signal(SIGBUS,no_card);		/* set up the error handler */
	setjmp(env);
	if (read_ok) reg = *dio_space;	/* try it */
	signal(SIGBUS,SIG_DFL);		/* turn it off */
	if ( !read_ok ) reg = -2;	/* bus error=no card installed */

	/* release the shared memory */
	if (ioctl(iomap_fd,IOMAPUNMAP,&shmem) < 0) return(-1);

	close(iomap_fd);
	return(reg);
}
no_card(sig)   /* should only be called on SIGBUS, a bus error */
int sig;
{
	if (sig != SIGBUS) exit(-1);
	read_ok = 0;
	longjmp(env,1);		/* avoid instruction restart on 68020s */
}

Once I am sure the open call succeeded, I can make the driver call (using
ioctl(2)).  I need to pass two things: the desired action (IOMAPMAP) and
where I want the card to be mapped in my user process space.  Passing an
address of 0 (done here) lets the system decide where to put it.  This
technique eventually makes shared memory calls, which have some other
implications on user address space.  
A full discussion of these implications is beyond the scope of this article,
but can be found in the
article "A Practical Look at the Series 300 Process Memory Map"
in the March/April 1986 issue of Tech Exchange for HPUX Systems.  The examples
presented in this (Device IO) article are simple enough to avoid any memory 
problems.

Notice that the actual read from the memory mapped space, "reg = *dio_space",
is surrounded by code to gracefully trap any bus errors.  A global variable,
read_ok, is set before, and tested after, the access to determine the success
or failure of the access.  A bus error (no card) will transfer control to
the no_card routine, which changes read_ok.  readio return a value of -2 
if there is no card.
-1 is returned if the open call failed (remember, first readio has to
deal with the file system).  A value in the range 0-255 is the actual
value returned from the card.

After compiling and linking these two modules you will have a program that 
prints the backplane configuration.  Note that you can use the full range of 
select codes (0-31) in HP-UX.  You may have noticed that select code 7  
was treated as a special case.
Due to a quirk in the hardware design of Series 300s (dating back to the 200),
the internal HPIB hardware resides at 0x478000, not 0x670000 as might be
expected.  Any configuration of current Series 300s without an internal
HPIB is unsupported.  Also, due to some other attributes of HP-UX, select
code 0 should not be used in a user application.

<header> The ASSIGN Statement

Consider a Rocky Mountain BASIC ASSIGN statement such as 

100  ASSIGN @Path to 16

This will look at the card at select code 16, determine its type (BCD, HPIB,
etc.), and attempt to assign a driver for it.  The variable @Path is called
an "IO path" and contains pointers to the card, the driver, and attribute
fields to be used for transfers.  Another form of ASSIGN is

100  ASSIGN @Path to 1605

By definition, there must be an HPIB card at select code 16; this statement
ASSIGNs @Path to the device at bus address 5.  

The final form of the ASSIGN statement for IO pathnames is

100  ASSIGN @Path to *

which terminates any association which was previously established.
In the first two cases, if there is no card, no driver, or any optional
attributes are incorrect or incompatible, the statement fails.  The ASSIGN
statement provides several advantages over using the device specifier:

1. Code is easier to read (i.e., @Voltmeter instead of 705) and maintain
   (change one ASSIGN statement instead of every occurrence of 705).
2. Card/driver validation and assignment is only done once; this provides
   for up to 30% speed improvement of interpreted program execution.
3. More attributes/formatting options are available.

How do we get an ASSIGN analogy in HP-UX?  Recall that all IO is performed
through device files.  A successful open in HP-UX will return a "file
descriptor" which is an integer used to identify the file in subsequent
operations.  This file descriptor serves as the analogy to the IO path.

The mknod(1M) command does no error checking or validation of the parameters
it is given; any problems will not show up until the file is actually 
opened.  Thus parameter validation and error recovery should be built into
an assign module.

Finally, the RMB ASSIGN statement can be given attributes which alter the
way data is OUTPUT/ENTERed.  The Device IO Library (DIL) of HP-UX implements
some of these attributes, and I have arbitrarily chosen to include two of them.
My assign statement is defined as

int assign(atname,ds,wide_mode,eoi_mode)
int *atname,	/* file descriptor, pass by reference */
    ds,		/* device specifier (like 12 or 205) */
    wide_mode,	/* WIDTH attribute, 0=8 bits, 1=16 */
    eoi_mode;	/* EOI attribute, 0=off, 1=on */

atname is a file descriptor which is returned from a successful open call to
a device file.  ds is the numeric device specifier.  wide_mode and eoi_mode
are the two attributes which are implemented with DIL calls.  Before listing
the code, I'd like to explain my assumptions/reasoning:

1. If ds is '*' (ASCII 42) assume *atname is a valid, open device file and
   close it.
2. ds > 100 implies an HPIB device; ds < 100 implies either GPIO or HPIB.
3. Use readio to determine the card type.  As DIL only supports HPIB and
   GPIO, return an error if the card is some other type.
4. GPIO cards must have device files of the form /dev/gpio_sc, 
   where sc is the select code.  For a GPIO card at select code 12,
   # mknod /dev/gpio_12 c 22 0x0c0000
5. "Raw" HPIB cards (select code only) must have device files of the form
   /dev/hpib_sc.  For an HPIB card (98624) at select code 2,
   # mknod /dev/hpib_02 c 21 0x021f00
   Although this looks like a bus address of 31, recall that only addresses
   0-30 are allowable on HPIB.
6. Auto address HPIB device files (select code and bus address) are of the form
   /dev/hpib_sc_ba.  For a card at select code 2 and a device at bus address 5,
   # mknod /dev/hpib_02_05 c 21 0x020500
7. eoi_mode only applies to HPIB devices.
8. wide_mode only applies to GPIO devices.

The mknod parameters can be found in the Configuration Reference Manual entries
for each card, or the HP-UX Concepts and Tutorials, Volume 4, Device IO and
User Interfacing.

When talking to an HPIB device, the talker/listener sequence must be executed
before data transmission occurs.  The HPIB driver will do this automatically
for autoaddress devices; the user must do it for raw devices.  The choice
is up to the discretion and requirements of the user.  Autoaddressing tends
to be much faster (fewer library calls by the user).  Raw mode is required
for most of the DIL calls and allows more explicit control of the bus 
(triggers, multiple listeners, etc.).

With all that in mind,

#include <fcntl.h>
#include <errno.h>
#include <dvio.h>

int assign(atname,ds,wide_mode,eoi_mode)
int *atname,	/* file descriptor, pass by reference */
    ds,		/* device specifier (like 12 or 205) */
    wide_mode,	/* WIDTH attribute, 0=8 bits, 1=16 */
    eoi_mode;	/* EOI attribute, 0=off, 1=on */
{
	extern int readio();

	int sc, ba, card_id;
	char devfilename[30];	/* a "string" */

	if (ds == '*') return(close(*atname));	/* will set errno */
	
	if ( ds / 100 ) {	/* something like 701 */
		sc = ds / 100;
		ba = ds % 100;
	}
	else {
		sc = ds;
		ba = -1;
	}
	if (sc < 1 || sc > 31) {
		errno = EINVAL;
		return(-1); 
	}
	*atname = -1; 	/* What the file system returns on error */

	if (sc == 7) 	/* supported configurations */
		card_id = 1;
	else
		card_id = readio(sc,1);  /* Is the card even there ? */

	if (card_id < 0) {
		if (card_id == -2) errno = ENODEV;  /* devfile ok, no card */
		return(-1);
	}
	if (card_id != 1 && card_id != 3) { 		/* unsupported by DIL */
		errno = EINVAL;
		return(-1);
	}
	if (card_id == 1) {				/* hpib */
		if (wide_mode) {
			errno = EINVAL;			/* per BASIC */
			return(-1);
		}
		if (ba == -1) 
			sprintf(devfilename,"/dev/hpib_%2.2d_raw",sc);
		else
			sprintf(devfilename,"/dev/hpib_%2.2d_%2.2d",sc,ba);
	}
	else						/* gpio */
		sprintf(devfilename,"/dev/gpio_%2.2d",sc);

	if ((*atname = open(devfilename,O_RDWR)) < 0) return(-1);
	if (card_id == 1) hpib_eoi_ctl(*atname,eoi_mode);
	if (card_id == 3 && wide_mode) io_width_ctl(*atname,16);
	return(0);
}

Keep in mind that using assign.c implicitly requires 1) device files for
the peripheral devices; 2) device files for the iomap devices; 3) shared
memory.  When assign successfully returns, atname represents a file descriptor
for a peripheral device.  The HP-UX write(2) and read(2) calls can use this
file descriptor to obtain most of the functionality of OUTPUT and ENTER in
RMB.

Discrepancies occur between the simplest forms of OUTPUT/ENTER and write/read.  
write(2) and read(2) require a
byte count; OUTPUT and ENTER do not.  Additionally, the IMAGE statement allows
a wide range of formatting to be performed (in addition to some length and
end-of-line conditions).  To handle formatting (i.e., internal REAL versus
ASCII) in HP-UX,

1. Use sprintf(3) to format data into buffers (character arrays) before using
   write(2).
2. Use sscanf(3) to "unformat" buffers returned by read(2).

There are several other image specifiers which can easily be handled in 
another module for HP-UX analogies to output and enter.  The declarations I 
have chosen are:

int output(atname,image,out_data)
int atname;
char *out_data, *image;

outdata points to a character buffer; image points to the IMAGE statement.
Allowable IMAGE items (which closely follow RMB) are:

W	Output one word
B	Output one byte
#	Suppress any end-of-line sequence
+	Use carriage return as the EOL character
-	Use linefeed as the EOL character

Lengths are handled by W/B, or *out_data being null-terminated (a C "string").

For the ENTER analogy,

int enter(atname,image,in_data)
int atname;
char *in_data,*image;

The allowable IMAGE items are

W	Enter one word (two bytes)
B	Enter one byte
#NNN	Enter NNN bytes (maximum)

The actual number of bytes read is based on W/B, #NNN, or the incoming data
being null terminated.  Any termination characters are stripped from
in_data (and reflected in the length).

#include <fcntl.h>
#include <errno.h>
#include <dvio.h>

int output(atname,image,out_data)
int atname;
char *out_data, *image;
{
	int word, byte, pound, plus, minus, buflen, i;
	char c, *buffer, *bufptr, *malloc();
	void free();
	
	word = 0;	/* clear image flags */
	byte = 0;
	pound = 0;
	plus = 0;
	minus = 0;
	while ((c = *image++) != '\0')	/* process the image string */
		switch (c) {
			case 'W': word = 1; break;
			case 'B': byte = 1; break;
			case '#': pound = 1; break;
			case '+': plus = 1; break;
			case '-': minus = 1; break;
			case ' ':
			case ',': break;
			default:  {
					errno = EINVAL;
					return(-1);
				}
		}

	if (word & byte || plus & minus) {    /* conflicting options */
		errno = EINVAL;
		return(-1);
	}
	if (byte) buflen = 1;
	else if (word) buflen = 2;
	     else buflen = strlen(out_data);  /* assume that it's string */
	
	/* build the output image including any termination characters */

	if ((buffer = malloc(buflen+2)) < 0) return(-1);
	bufptr = buffer;
	for (i = 1; i <= buflen; i++) *bufptr++ = *out_data++;

	if (!pound) {
		if (plus) {
			*bufptr++ = '\r';	/* carriage return CHR$(13) */
			buflen++;
		}
		if (minus) {
			*bufptr++ = '\n';	/* line feed CHR$(10) */
			buflen++;
		}
		if (!(plus || minus)) { 
			*bufptr++ = '\r';
			*bufptr++ = '\n';
			buflen += 2;
		}
			
	}
	if (write(atname,buffer,buflen) < 0) return(-1);
	free(buffer);
	return(0);
}
int enter(atname,image,in_data)
int atname;
char *in_data,*image;
{
#define 	MAXBUF	1000
	int word, byte, pound, buflen, i;
	char c, *buffer, *bufptr, *malloc();
	void free();
	
	word = 0;
	byte = 0;
	pound = 0;
	buflen = 0;
	while ((c = *image++) != '\0')
		switch (c) {
			case 'W': word = 1; break;
			case 'B': byte = 1; break;
			case '#': {
				if (pound) {	/* already specified */
					errno = EAGAIN;
					return(-1);
				}
				pound = 1;
				break;
			}
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9': {
				if ( !pound ) {
					errno = EINVAL;
					return(-1);
				}
				buflen *= 10;
				buflen += c - '0';
				break;
			}
			case ',':
			case ' ': break;
			default:  {
					errno = EINVAL;
					return(-1);
				}
		}
	if (pound && (byte || word) || buflen > MAXBUF) {
		errno = EINVAL;
		return(-1);
	}
	if (byte) buflen = 1;
	else if (word) buflen = 2;
	else if ( !pound) {
		if (io_eol_ctl(atname,1,'\n') < 0) return(-1);
		buflen = MAXBUF;
	}
	if ((buffer = malloc(buflen)) < 0) return(-1);

	if ((i = read(atname,buffer,buflen)) < 0) return(-1);

	if ( !pound ) {  /* The eol character is stored at the end of the
			    buffer.  If one of the termination reasons is
			    eol (coupled with EOI, maybe) then I can remove
			    the eol char.  At this point I've assumed the 
			    buffer is a string so null-terminate it. */
		if ((buflen = io_get_term_reason(atname)) < 0) return(-1);
		if (buflen | 2) {
			bufptr = buffer + i - 1;
			*bufptr = '\0';
		}
	}
	buflen = i;
	bufptr = buffer;
	while (i > 0) {
		*in_data++ = *bufptr++;
		i--;
	}
	free(buffer);
	return(buflen);
}

So what good is all this stuff?  Consider the following simple BASIC program
to set up a 3437A voltmeter and take 400 readings (which are transmitted
in ASCII):

10    REAL Volts
20    INTEGER I
30    ASSIGN @Dvm TO 724
40    OUTPUT @Dvm;"D0SN1SE0S"
50    OUTPUT @Dvm;"R2T1F1"
60    FOR I=1 TO 400
70      ENTER @Dvm;Volts
80      PRINT I,Volts
90    NEXT I
100   END

Using all the code presented above,

#include <stdio.h>
main()                  /* Make readings from a 3437A */
{
        float volts;
        int i, dvm;
        char temp[30];
        extern int assign(), output(), enter(), sscanf();

        if (assign(&dvm,224,0,0) < 0) perror("Can't assign to 224");
        output(dvm,"","D0SN1SE0S");
        output(dvm,"","R2T1F1");
        for (i = 1; i <= 400; i++) {
                enter(dvm,"",temp);             /* ASCII characters */
                sscanf(temp,"%f",&volts);       /* Format to a real */
                printf("%d  %f\n",i,volts);     /* Print the real */
        }
        assign(&dvm,'*',0,0);
}

The ASCII/REAL/ASCII conversions are somewhat redundant, but show how to
do the formatting previously mentioned.

There are many other DIL calls and HP-UX capabilities not covered here,
like interrupts and real time priorities.  I hope this "starter kit" will
be of help to both BASIC programmers looking at HP-UX, and C programmers
attempting IO.

bobk@hpcuha.cup.hp.com (Bob Kentwortz) (11/01/90)

I'm not very familiar with this subject, but I've seen a manual: Device I/O
and user Interfacing  (p/n 97089-90057)  which may be helpful.  This manual,
also called DIL, has a large section with examples on "Controlling the HP-IB
interface."  This information applies to both 300 and 800 series systems, 
with dependencies in the rear of the manual.  There may also be information
in the hpib man pages on the system on the ioctl calls available for hpib.
(DIL uses the ioctl calls, so it will be a bit slower then using them
directly.)

Hope this is of some use.

Bob Kentwortz