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