├── CHANGELOG.md ├── GPIB.ino └── README.md /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 6.1 (March 3, 2014) 2 | 3 | - bug fix especially at command interpreter level ; 4 | 5 | 6 | - command syntax is now compatible with the `++` language; 7 | 8 | - for compatibility reasons the controller is now much more silent when executing commands (but see the `++verbose` command below); 9 | 10 | - reads from GPIB are not buffered anymore: char received from GPIb are sent to USB a soon as they are received. This enable this version of the controller to receive bulk binary data; 11 | 12 | - for compatibility reasons the escape char has been changed from `\` to Ascii ESC (0x1B); 13 | 14 | - `++addr` has a new default value of 2; 15 | 16 | - `++eot_char` and `++eot_enable` commands have been implemented with default values of 10 (0xA) and 0 respectively; 17 | 18 | - `++auto` is no more a toggle and is fully compatible now. The new syntax is `++auto [0|1]` and the default is 0; 19 | 20 | - `++eoi` has been implemented with syntax `++eoi [0|1]` and a default value of 1; 21 | 22 | - `++eos` has been implemented with syntax `++eos [0|1|2|3`] and a default value is 1; 23 | 24 | - `++read` is completely unbuffered now. It terminates in front of an EOI signal detect OR an EOS char received from GPIB. `++read eoi` changes this logic to EOI detection only. This is useful (and works) for binary transfers - e.g. screen dumps. 25 | 26 | - `++ver` returns ` the string `ARDUINO GPIB firmware by E. Girlando Version 6.1 `. 27 | 28 | - `++read_tmo_ms` has been implemented with syntax `++read_tmo-ms [100-9999]` and a default value of 200; 29 | 30 | Two additional proprietary commands (not usually included in the `++` set) have been implemented: 31 | 32 | - `++verbose` this command toggles the controller between verbose and silent mode. When in verbose mode the controller assumes an human is working on the session: a `>` prompt is issued to the USB side of the session every time the controller is ready to accept a new command and most of the `++` commands now print a useful feedback answers (i.g. the new set value or error messages). This breaks the full `++` compatibility. Do not use verbose mode when trying to use third party software. Default is verbose OFF. 33 | 34 | - `++dcl` sends an unaddressed DCL (Universal Device Clear) command to the GPIB. It is a message to the device rather than to its interface. So it is left to the device how to react on a DCL. Typically it generates a power on reset. 35 | This version of the controller successfully reacts to the Prologic.exe program included in the KE5FX GPIB toolkit. I was not able to test the 7470.exe program as I do not have any instrument compatible with the toolkit (I indeed tryed with my TEK 2232 scope, but it failed...). 36 | -------------------------------------------------------------------------------- /GPIB.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Arduino USB to GPIB firmware by E. Girlando is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. 3 | Permissions beyond the scope of this license may be available at emanuele_girlando@yahoo.com. 4 | */ 5 | 6 | // #define DEBUG_BUILD 7 | // #define DEEP_DEBUG_BUILD 8 | 9 | 10 | /* 11 | Implements some of the CONTROLLER functions; CIC only; 12 | Almost compatible with the "++" command language present in other de facto standard professional solutions. 13 | DEVICE functions: TODO (the listen part is needed to get bulk binary data from instruments.. -such as screen dumps). 14 | The talker function I cannot imagine what can be useful for. 15 | 16 | Principle of operation: 17 | Receives commands from the USB in (buffered) lines, 18 | if "++" command call the handler, 19 | otherwise sends it to the GPIB bus. 20 | To receive from GIPB you must issue a ++read command or put the controller in auto mode with ++auto 1. 21 | The receiver is unbeffered (byte are senti to USB as soon as they are received). 22 | */ 23 | 24 | /* 25 | Together with the comments aside,these definitions define the mapping between the Arduino pins and the GPIB connector. 26 | It actually defines the hardware connections required for this sketch to work. 27 | NOTE: 28 | GPIB pins 10, 17-24 goto GND, but 29 | GPIB pin 17 (REN) has to be investigated further.. 30 | GPIB pin 10 (SRQ) has to be investigated further.. 31 | GPIB pin 12 should be connected to the cable shield (not used here - leave it n/c) 32 | */ 33 | #define DIO1 A0 /* GPIB 1 : I/O data bit 1 - Arduino PORTC bit 0 */ 34 | #define DIO2 A1 /* GPIB 2 : I/O data bit 2 - Arduino PORTC bit 1 */ 35 | #define DIO3 A2 /* GPIB 3 : I/O data bit 3 - Arduino PORTC bit 2 */ 36 | #define DIO4 A3 /* GPIB 4 : I/O data bit 4 - Arduino PORTC bit 3 */ 37 | #define DIO5 A4 /* GPIB 13 : I/O data bit 5 - Arduino PORTC bit 4 */ 38 | #define DIO6 A5 /* GPIB 14 : I/O data bit 6 - Arduino PORTC bit 5 */ 39 | #define DIO7 4 /* GPIB 15 : I/O data bit 7 - Arduino PORTD bit 4 */ 40 | #define DIO8 5 /* GPIB 16 : I/O data bit 8 - Arduino PORTD bit 5 */ 41 | #define EOI 12 /* GPIB 5 : End Or Identify - Arduino PORTB bit 4 */ 42 | #define DAV 11 /* GPIB 6 : Data Valid - Arduino PORTB bit 3 */ 43 | #define NRFD 10 /* GPIB 7 : Not Ready For Data - Arduino PORTB bit 2 */ 44 | #define NDAC 9 /* GPIB 8 : Not Data Accepted - Arduino PORTB bit 0 */ 45 | #define IFC 8 /* GPIB 9 : Interface Clear - Arduino PORTB bit 1 */ 46 | #define ATN 7 /* GPIB 11 : Attention - Arduino PORTD bit 7 */ 47 | /* 48 | NOTE for the entire code: 49 | Remember GPIB logic: HIGH means not active, deasserted; LOW means active, asserted; 50 | also remember that ATmega's pins set as inputs are hi-Z (unless you enable the internal pullups). 51 | */ 52 | /* 53 | GPIB BUS commands 54 | */ 55 | // Universal Multiline commands - all devices affected. No need to address a destination. 56 | #define DCL 0x14 57 | #define LLO 0x11 58 | #define UNL 0x3F 59 | #define UNT 0x5F 60 | #define SPE 0x18 61 | #define SPD 0x19 62 | #define PPU 0x15 63 | // Addressed commands 64 | #define SDC 0x04 65 | #define GTL 0x01 66 | #define GET 0x08 67 | 68 | 69 | 70 | 71 | #define SUCCESS false 72 | #define FAIL true 73 | #define IN 1 74 | #define OUT 0 75 | #define CONTROLLER true 76 | #define DEVICE false 77 | /* 78 | ***** GLOBAL BUFFERS 79 | */ 80 | #define BUFFSIZE 128 // too short? 81 | char com[BUFFSIZE] = "", // USB input string buffer 82 | *comp = com, // pointer floating in com buffer 83 | *come = com + BUFFSIZE - 1; // pointer to the far end of com buffer 84 | // NOTE: use of ponters rather than indexes is a personal (BAD) choice: it can lead to potential portability issues, but that's it. 85 | 86 | 87 | #define ESC 0x1B // the USB escape char for "+"s, CRs and NLs. 88 | #define CR 0xD 89 | #define NL 0xA 90 | #define PLUS 0x2B 91 | char *EOSs = "\r\n"; // string containing a list of all possible GPIB terminator chars. 92 | boolean itwascmd=false; // flag to know if the last USB input line was a "++" command or not. 93 | 94 | /* 95 | Controller state variables. 96 | They should be collected in a struct...and acconpained whth reoutines to save and load them to/from EEPROM 97 | 98 | Their values are initialized here and changes are managed by "++" command handlers 99 | */ 100 | #define MYADDR 5 // my gpib adress - The Controller-In-Charge (me in this case) for a bus is almost always at PAD 0 */ 101 | int htimeout = 200; // Handshake timeout. Also known as read_tmo_ms. Its actual value has to be found by 102 | // trial&error depending on the actual application. 103 | boolean cmode = CONTROLLER; // controller mode (never changed at the moment.. only controller mode implemented) 104 | byte addr = 2; // GPIB address of actual communication counterpart (tipically a device or instrument). 105 | 106 | byte eos=1; // end of string flag to control how messages to GPIB are terminated. 107 | boolean eoi=true; // by default asserts EOI on last char written to GPIB 108 | boolean eoi_only=false; // GPIB input string termination: default is by (EOI || EOS) but "++read eoi" requires an eoi only behavior. 109 | boolean eot_enable=false; // do we append a char to received string before sending it to USB? 110 | char eot_char = 0xA; // if eot_enable this is the char in use. 111 | boolean automode = false; // read after write 112 | boolean verbose=false; // if set the controller expect a human interaction but loose compatibility; silent otherwise but with more compatibility. 113 | /* 114 | end of Controller state variables 115 | */ 116 | 117 | 118 | void setup() { 119 | 120 | Serial.begin(115200); 121 | flush_serial(); 122 | 123 | // initialize gpib lines 124 | pinMode(ATN, OUTPUT); 125 | digitalWrite(ATN, HIGH); 126 | pinMode(EOI, OUTPUT); 127 | digitalWrite(EOI, HIGH); 128 | pinMode(DAV, OUTPUT); 129 | digitalWrite(DAV, HIGH); 130 | pinMode(NDAC, OUTPUT); 131 | digitalWrite(NDAC, LOW); 132 | pinMode(NRFD, OUTPUT); 133 | digitalWrite(NRFD, LOW); 134 | pinMode(IFC, OUTPUT); 135 | digitalWrite(IFC, HIGH); 136 | (void) get_dab(); // just to set all data lines in a known not floating state.. 137 | 138 | if (verbose) print_ver(); 139 | 140 | #ifdef DEBUG_BUILD 141 | Serial.println(F("Debug enable")); 142 | #endif 143 | 144 | } 145 | 146 | // sometime things go wrong (e.g. buffer overflows, syntax erros in "++" cmds) and 147 | // we do need to discard everything left pending in the serial buffer..) 148 | void flush_serial(void) { 149 | while( Serial.available() != 0 ) { 150 | 151 | #ifdef DEBUG_BUILD 152 | Serial.print(F("FLUSHED 0x")); 153 | Serial.println(Serial.read(),HEX); 154 | #else 155 | Serial.read(); 156 | #endif 157 | } 158 | } 159 | 160 | 161 | /////////////////////////////////////////////////////////////////// 162 | /////////////////////////////////////////////////////////////////// 163 | /////////// MAIN LOOP 164 | /////////////////////////////////////////////////////////////////// 165 | /////////////////////////////////////////////////////////////////// 166 | void loop() { 167 | 168 | flush_serial(); // ensure nothing is pending just before prompting the user.. 169 | if (verbose) Serial.print("> "); // humans like to be promted for input... 170 | 171 | getUSBline(); // gets the next USB input line and if it is a "++" command exec the handler. 172 | 173 | if (*com) { // if somthing arrived .. and it should... 174 | #ifdef DEBUG_BUILD 175 | Serial.print("Sending "); 176 | Serial.print(com); 177 | Serial.println(" to GPIB BUS"); 178 | #endif 179 | gpibTalk(com); 180 | } 181 | 182 | if (*com && automode && !itwascmd) gpibReceive(); else if (verbose) Serial.println(); 183 | 184 | *com = NULL; itwascmd=false; 185 | } // end loop 186 | 187 | void getUSBline() { 188 | static byte c = 0; 189 | static boolean isesc=false; // escape flag 190 | static byte gotplus=0; // '+' counter 191 | do { 192 | 193 | if (Serial.available()>0) { 194 | if (Serial.available() >=63) { // chars coming in too fast.... 195 | if (verbose) Serial.println(F("Serial buffer overflow - line discarded. Try setting non buffered I/O in your USB data source")); 196 | *com = NULL; comp = com; 197 | return; 198 | } 199 | 200 | c=Serial.read(); 201 | 202 | #ifdef DEEP_DEBUG_BUILD 203 | Serial.print("0x"); 204 | Serial.print(c, HEX); 205 | Serial.print(": "); 206 | Serial.print((char)c); 207 | Serial.print(F(" - Serial buffer = ")); Serial.print(Serial.available()); 208 | Serial.print(" - Pluses: "); 209 | Serial.println(gotplus); 210 | #endif 211 | 212 | switch ( c ) { 213 | 214 | case ESC: 215 | #ifdef DEBUG_BUILD 216 | Serial.println(F("ESC!!!")); 217 | #endif 218 | if (isesc) goto loadchar; // escaped escape (!) 219 | else isesc = true; 220 | break; 221 | 222 | case PLUS: 223 | if (isesc) goto loadchar; // escaped '+' 224 | if (comp == com && gotplus <2) gotplus++; // only if comp has not yet been moved (++ at the beginning..) 225 | break; 226 | 227 | case CR: 228 | case NL: 229 | // if (isesc) goto loadchar; 230 | if (isesc) { Serial.println("CR or LF inserted");goto loadchar; } 231 | 232 | *comp = NULL; // replace USBeos with null 233 | if (gotplus == 2) { // got a "++" at the beginnig: its a command! 234 | cmdparse(); // parse it 235 | itwascmd=true; 236 | } 237 | comp = com; isesc=false; gotplus=0; 238 | return; 239 | break; 240 | 241 | default: 242 | loadchar: // collect c in com, ensuring com does not overflow */ 243 | if ( comp < come ) { *comp++ = c; } 244 | else { // buffer overflow; entire line discarded 245 | Serial.print(F("USB buffer overflow: limit input size to ")); Serial.print(BUFFSIZE-1); Serial.println(" chars."); 246 | //flush_serial(); 247 | *com = NULL; comp = com; isesc=false; 248 | return; 249 | } 250 | isesc=false; 251 | } //end switch 252 | } // end if serial.available() 253 | } while(1); 254 | Serial.print(F("ASSERT error: statement should be never reached in getUSBline()")); 255 | } 256 | 257 | /* 258 | command parser 259 | */ 260 | struct cmd_info { 261 | char* opcode; 262 | void (*handler)(void); 263 | }; 264 | 265 | static cmd_info cmds [] = { 266 | { "addr", addr_h }, 267 | { "ver" , print_ver }, 268 | { "verbose" , verbose_h }, 269 | { "mode" , mode_h }, 270 | { "read_tmo_ms", tmo_h }, 271 | { "auto", automode_h }, 272 | { "read", read_h }, 273 | { "clr", clr_h }, 274 | { "trg", trg_h }, 275 | { "llo", llo_h }, 276 | { "loc", loc_h }, 277 | { "eos", eos_h }, 278 | { "eoi", eoi_h }, 279 | { "eot_enable", eot_enable_h }, 280 | { "eot_char", eot_char_h }, 281 | { "dcl", dcl_h }, 282 | { "ifc", ifc_h } 283 | }; 284 | 285 | /* 286 | Called when a "++" has been founf at the beginning of USB input line 287 | */ 288 | void cmdparse() { 289 | cmd_info *cmdp, 290 | *cmde = cmds + sizeof( cmds ) / sizeof( cmds[0] ); 291 | char *param; // pointer to comman parameter(s) 292 | 293 | #ifdef DEBUG_BUILD 294 | Serial.print("Parse: "); 295 | Serial.print(com); Serial.print(" - lengh:");Serial.println(strlen(com)); 296 | #endif 297 | 298 | if (*com == (NULL || CR || NL) ) return; // empty line: nothing to parse. 299 | 300 | param=strtok(com, " \t"); // additional calls to strtok possible in handlers routines 301 | for (cmdp = cmds; cmdp != cmde; cmdp++ ) { 302 | #ifdef DEEP_DEBUG_BUILD 303 | Serial.print("Parse: "); 304 | Serial.print(cmdp->opcode); Serial.print(" Vs ");Serial.println(param); 305 | #endif 306 | if( 0 == strcmp(cmdp->opcode, param) ) { // found a valid command 307 | (*cmdp->handler)(); // call the corresponding handler 308 | break; // done. 309 | } 310 | } 311 | 312 | if (cmdp == cmde && verbose) { 313 | Serial.print(param); Serial.println(F(": unreconized command.")); 314 | } 315 | 316 | *com=NULL; comp=com; //Done. 317 | 318 | } 319 | 320 | 321 | /* 322 | command handlers 323 | */ 324 | void addr_h() { 325 | char *param, *pend; 326 | int temp; 327 | 328 | if ( (param=strtok(NULL, " \t")) ) { 329 | // Serial.print("param: "); Serial.println(param); 330 | temp = strtol(param, &pend, 10); 331 | if (*pend != NULL) { if (verbose) Serial.println(F("Syntax error.")); return;} 332 | if (temp<1 || temp > 30) { 333 | if (verbose) Serial.println(F("address out of range: valid address range is [1-30]."));return;} 334 | addr=temp; if (!verbose) return; 335 | } 336 | Serial.println(addr); 337 | } 338 | 339 | void tmo_h() { 340 | char *param, *pend; 341 | int temp; 342 | 343 | if ( (param=strtok(NULL, " \t")) ) { 344 | // Serial.print("param: "); Serial.println(param); 345 | temp = strtol(param, &pend, 10); 346 | if (*pend != NULL) { if (verbose) Serial.println(F("Syntax error.")); return;} 347 | if (temp<100 || temp > 9999) { 348 | if (verbose) Serial.println(F("read_tmo_ms out of range: valid address range is [100-9999]."));return;} 349 | htimeout=temp; if (!verbose) return; 350 | } 351 | Serial.println(htimeout); 352 | } 353 | void eos_h() { 354 | char *param, *pend; 355 | int temp; 356 | 357 | if ( (param=strtok(NULL, " \t")) ) { 358 | // Serial.print("param: "); Serial.println(param); 359 | temp = strtol(param, &pend, 10); 360 | if (*pend != NULL) { if (verbose) Serial.println(F("Syntax error.")); return;} 361 | if (temp<0|| temp > 3) { 362 | if (verbose) Serial.println(F("eos out of range: valid address range is [0-3]."));return;} 363 | eos=temp; if (!verbose) return; 364 | } 365 | Serial.println(eos); 366 | } 367 | void eoi_h() { 368 | char *param, *pend; 369 | int temp; 370 | 371 | if ( (param=strtok(NULL, " \t")) ) { 372 | temp = strtol(param, &pend, 10); 373 | if (*pend != NULL) { if (verbose) Serial.println(F("Syntax error.")); return;} 374 | if (temp<0|| temp > 1) { 375 | if (verbose) Serial.println(F("eoi: valid address range is [0|1]."));return;} 376 | eoi = temp ? true : false; if (!verbose) return; 377 | } 378 | Serial.println(eoi); 379 | } 380 | void mode_h() { 381 | char *param, *pend; 382 | int temp; 383 | 384 | if ( (param=strtok(NULL, " \t")) ) { 385 | temp = strtol(param, &pend, 10); 386 | if (*pend != NULL) { if (verbose) Serial.println(F("Syntax error.")); return;} 387 | if (temp<0|| temp > 1) { 388 | if (verbose) Serial.println(F("mode: valid address range is [0|1]."));return;} 389 | // cmode = temp ? CONTROLLER : DEVICE; if (!verbose) return; 390 | cmode=CONTROLLER; // only controller mode implemented here... 391 | } 392 | Serial.println(cmode); 393 | } 394 | void eot_enable_h() { 395 | char *param, *pend; 396 | int temp; 397 | 398 | if ( (param=strtok(NULL, " \t")) ) { 399 | temp = strtol(param, &pend, 10); 400 | if (*pend != NULL) { if (verbose) Serial.println(F("Syntax error.")); return;} 401 | if (temp<0|| temp > 1) { 402 | if (verbose) Serial.println(F("eoi: valid address range is [0|1]."));return;} 403 | eot_enable = temp ? true : false; if (!verbose) return; 404 | } 405 | Serial.println(eot_enable); 406 | } 407 | void eot_char_h() { 408 | char *param, *pend; 409 | int temp; 410 | 411 | if ( (param=strtok(NULL, " \t")) ) { 412 | temp = strtol(param, &pend, 10); 413 | if (*pend != NULL) { if (verbose) Serial.println(F("Syntax error.")); return;} 414 | if (temp<0|| temp > 256) { 415 | if (verbose) Serial.println(F("eot_char: valid address range is [0-256]."));return;} 416 | eot_char = temp; if (!verbose) return; 417 | } 418 | Serial.println((byte)eot_char); 419 | } 420 | 421 | void automode_h() { 422 | char *param, *pend; 423 | int temp; 424 | 425 | if ( (param=strtok(NULL, " \t")) ) { 426 | temp = strtol(param, &pend, 10); 427 | if (*pend != NULL) { if (verbose) Serial.println(F("Syntax error.")); return;} 428 | if (temp<0|| temp > 1) { 429 | if (verbose) Serial.println(F("automode: valid address range is [0|1]."));return;} 430 | automode = temp ? true : false; if (!verbose) return; 431 | !automode ? 0 : Serial.println(F("WARNING: automode ON can generate \"addressed to talk but nothing to say\" errors in the devices \nor issue read command too soon.")); 432 | } 433 | Serial.println(automode); 434 | } 435 | 436 | void print_ver() { 437 | Serial.println(F("ARDUINO GPIB firmware by E. Girlando Version 6.1")); 438 | } 439 | void read_h() { 440 | char *param, *pend; 441 | int temp; 442 | 443 | if ( (param=strtok(NULL, " \t")) ) 444 | if (0 == strncmp(param, "eoi", 3)) eoi_only=true; 445 | gpibReceive(); 446 | eoi_only=false; 447 | } 448 | 449 | 450 | void clr_h() { 451 | if ( sendGPIB_Acmd(SDC) ) { 452 | Serial.println(F("clr_h: sendGPIB_Acmd failed")); 453 | return; 454 | } 455 | } 456 | void llo_h() { 457 | if ( sendGPIB_Ucmd(LLO) ) { 458 | Serial.println(F("llo_h: sendGPIB_Ucmd failed")); 459 | return; 460 | } 461 | } 462 | 463 | /* 464 | The (Universal) Device Clear (DCL) can be sent by any active controller and is recognized by all devices, 465 | however it is a message to the device rather than to its interface. 466 | So it is left to the device how to react on a Universal Device Clear. 467 | There is no assumption (at least not in IEEE-488.1) what the device should do (it can even ignore the DCL). 468 | */ 469 | void dcl_h() { 470 | if ( sendGPIB_Ucmd(DCL) ) { 471 | Serial.println(F("dcl_h: sendGPIB_Ucmd failed")); 472 | return; 473 | } 474 | } 475 | void loc_h() { 476 | if ( sendGPIB_Acmd(GTL) ) { 477 | Serial.println(F("loc_h: sendGPIB_Acmd failed")); 478 | return; 479 | } 480 | } 481 | 482 | /* 483 | The Interface Clear (IFC) is conducted by asserting the IFC line for at least 100 milliseconds, 484 | which is reserved to the system controller. 485 | The defined effect is that all interfaces connected to the bus return to their idle state 486 | (putting the whole bus into a quiescent state), 487 | and the controller role is returned to the system controller (if there is another controller active). 488 | */ 489 | void ifc_h() { 490 | gpibIFC(); 491 | } 492 | 493 | void trg_h() { 494 | if ( sendGPIB_Acmd(GET) ) { 495 | Serial.println(F("trg_h: sendGPIB_Acmd failed")); 496 | return; 497 | } 498 | 499 | } 500 | 501 | void verbose_h() { 502 | verbose = !verbose; 503 | Serial.print("verbose: "); 504 | Serial.println(verbose ? "ON" : "OFF"); 505 | } 506 | /* 507 | end of command handlers 508 | */ 509 | 510 | /////////////////////////////////////////////////////////////////// 511 | /////////////////////////////////////////////////////////////////// 512 | /////////// GPIB LOW LEVEL ROUTINES 513 | /////////////////////////////////////////////////////////////////// 514 | /////////////////////////////////////////////////////////////////// 515 | #ifdef DEBUG_BUILD 516 | void gpibLineStatus(void) { 517 | pinMode(ATN, INPUT_PULLUP); 518 | Serial.print(" ATN="); 519 | Serial.print(digitalRead(ATN)); 520 | pinMode(DAV, INPUT_PULLUP); 521 | Serial.print(", DAV="); 522 | Serial.print(digitalRead(DAV)); 523 | pinMode(NRFD, INPUT_PULLUP); 524 | Serial.print(", NRFD="); 525 | Serial.print(digitalRead(NRFD)); 526 | pinMode(NDAC, INPUT_PULLUP); 527 | Serial.print(", NDAC="); 528 | Serial.print(digitalRead(NDAC)); 529 | pinMode(EOI, INPUT_PULLUP); 530 | Serial.print(", EOI="); 531 | Serial.print(digitalRead(EOI)); 532 | Serial.print(", DIO8-1="); 533 | pinMode(DIO8, INPUT_PULLUP); 534 | Serial.print(digitalRead(DIO8)); 535 | pinMode(DIO7, INPUT_PULLUP); 536 | Serial.print(digitalRead(DIO7)); 537 | pinMode(DIO6, INPUT_PULLUP); 538 | Serial.print(digitalRead(DIO6)); 539 | pinMode(DIO5, INPUT_PULLUP); 540 | Serial.print(digitalRead(DIO5)); 541 | pinMode(DIO4, INPUT_PULLUP); 542 | Serial.print(digitalRead(DIO4)); 543 | pinMode(DIO3, INPUT_PULLUP); 544 | Serial.print(digitalRead(DIO3)); 545 | pinMode(DIO2, INPUT_PULLUP); 546 | Serial.print(digitalRead(DIO2)); 547 | pinMode(DIO1, INPUT_PULLUP); 548 | Serial.println(digitalRead(DIO1)); 549 | 550 | //reset pins at inital status 551 | pinMode(ATN, OUTPUT); 552 | digitalWrite(ATN, HIGH); 553 | pinMode(EOI, OUTPUT); 554 | digitalWrite(EOI, HIGH); 555 | pinMode(DAV, OUTPUT); 556 | digitalWrite(DAV, HIGH); 557 | pinMode(NDAC, OUTPUT); 558 | digitalWrite(NDAC, LOW); 559 | pinMode(NRFD, OUTPUT); 560 | digitalWrite(NRFD, LOW); 561 | } 562 | #endif 563 | 564 | /* 565 | ********** GPIB LOW LOW level routines 566 | */ 567 | /* 568 | The following two functions read/write data from the GPIB. 569 | In version 1 they where implemented by a sequence of pinmode(), pinwrite(9 and pinread(). 570 | Here in version 2 they are implemented by bitwise operations directly on the Arduino register PORTs. 571 | The old code remain commented to give an idea of what the bitwise ops are supposed to do. 572 | 573 | Theory of the following bitwise operations: 574 | We want to move some bit from v2 to v1, leaving the other bits in v1 unchanged (all variables unsigned!). 575 | M is the mask of the target bits in v1. It contains 1s in the positions where the bits to transfer are 576 | in v1). e.g M=0b00110000 (..want to move bits 4 and 5). 577 | The statement: 1 = (v1 & ~M) | (v2 & M) 578 | will take bits 4 and 5 from v2 and inserts them in v1 leaving the other v1 bits unchanged. 579 | 580 | For meaning of DDRD, DDRC, PORTD and PORTD see the Arduino documentation. 581 | 582 | References to variable x in set_dab and the return value in get_dab are inverted because of the GPIB logic. 583 | 584 | Here the "DAB" mnemonic is used to indicate a data line transfer, either actual data bytes (DABs) or command bytes 585 | 586 | */ 587 | byte get_dab() { 588 | /* 589 | pinMode(DIO1, INPUT_PULLUP); bitWrite(x, 0, !digitalRead(DIO1)); 590 | pinMode(DIO2, INPUT_PULLUP); bitWrite(x, 1, !digitalRead(DIO2)); 591 | pinMode(DIO3, INPUT_PULLUP); bitWrite(x, 2, !digitalRead(DIO3)); 592 | pinMode(DIO4, INPUT_PULLUP); bitWrite(x, 3, !digitalRead(DIO4)); 593 | pinMode(DIO5, INPUT_PULLUP); bitWrite(x, 4, !digitalRead(DIO5)); 594 | pinMode(DIO6, INPUT_PULLUP); bitWrite(x, 5, !digitalRead(DIO6)); 595 | pinMode(DIO7, INPUT_PULLUP); bitWrite(x, 6, !digitalRead(DIO7)); 596 | pinMode(DIO8, INPUT_PULLUP); bitWrite(x, 7, !digitalRead(DIO8)); 597 | */ 598 | 599 | DDRD = DDRD & 0b11001111 ; 600 | PORTD = PORTD | 0b00110000; // PORTD bits 5,4 input_pullup 601 | DDRC = DDRC & 0b11000000 ; 602 | PORTC = PORTC | 0b00111111; // PORTC bits 5,4,3,2,1,0 input_pullup 603 | return ~((PIND<<2 & 0b11000000)+(PINC & 0b00111111)); 604 | } 605 | void set_dab(byte x) { 606 | /* 607 | pinMode(DIO1, OUTPUT); digitalWrite(DIO1, bitRead(~x, 0)); 608 | pinMode(DIO2, OUTPUT); digitalWrite(DIO2, bitRead(~x, 1)); 609 | pinMode(DIO3, OUTPUT); digitalWrite(DIO3, bitRead(~x, 2)); 610 | pinMode(DIO4, OUTPUT); digitalWrite(DIO4, bitRead(~x, 3)); 611 | pinMode(DIO5, OUTPUT); digitalWrite(DIO5, bitRead(~x, 4)); 612 | pinMode(DIO6, OUTPUT); digitalWrite(DIO6, bitRead(~x, 5)); 613 | pinMode(DIO7, OUTPUT); digitalWrite(DIO7, bitRead(~x, 6)); 614 | pinMode(DIO8, OUTPUT); digitalWrite(DIO8, bitRead(~x, 7)); 615 | */ 616 | DDRD = DDRD | 0b00110000; 617 | DDRC = DDRC | 0b00111111; 618 | PORTD = ( (PORTD&0b11001111) | (~x>>2 & 0b00110000) ) ; 619 | PORTC = ( (PORTC&0b11000000) | (~x & 0b00111111) ) ; 620 | } 621 | 622 | 623 | // wait for "pin" to go at "level" or "timeout". 624 | // return values: false on success, true on timeout. 625 | boolean Wait_level_on_pin(byte level, byte pin, int timeout) { 626 | int s_time, c_time; 627 | 628 | pinMode(pin, INPUT_PULLUP); 629 | s_time = millis(); 630 | c_time = s_time; 631 | while (level == digitalRead(pin)) { 632 | if( (c_time - s_time)>timeout ) return true; 633 | else c_time=millis(); 634 | } 635 | return false; //success! 636 | } 637 | 638 | /* 639 | Source role. 640 | Write a single char on gpib managing the gpib 3wires handshake protocol with timeouts. 641 | See Intel AP-166; Figure2. 642 | 643 | Return values: 644 | 0 on success, 645 | 1 on timeout. 646 | */ 647 | boolean gpibWrite(byte data) { 648 | // prepare to for handshaking 649 | pinMode(DAV, OUTPUT); 650 | 651 | #ifdef DEEP_DEBUG_BUILD 652 | Serial.print("sending data:0x"); 653 | Serial.print(data, HEX); 654 | Serial.print("("); 655 | Serial.print((char)data); 656 | Serial.print(") "); 657 | #endif 658 | 659 | // prepare to for handshaking 660 | digitalWrite(DAV, HIGH); 661 | 662 | //wait until (LOW == NDAC) 663 | if (Wait_level_on_pin(HIGH,NDAC,htimeout)) { 664 | if (verbose) Serial.println(F("gpibWrite: timeout waiting NDAC")); 665 | return true; 666 | } 667 | #ifdef DEEP_DEBUG_BUILD 668 | Serial.print("W1"); 669 | #endif 670 | // output data to DIO 671 | set_dab(data); 672 | 673 | //wait until (HIGH == NRFD) 674 | if (Wait_level_on_pin(LOW,NRFD,htimeout)) { 675 | if (verbose) Serial.println(F("gpibWrite: timeout waiting NRFD")); 676 | return true; 677 | } 678 | #ifdef DEEP_DEBUG_BUILD 679 | Serial.print(" W2"); 680 | #endif 681 | 682 | // confirm data is valid 683 | digitalWrite(DAV, LOW); 684 | #ifdef DEEP_DEBUG_BUILD 685 | Serial.print(" W3"); 686 | #endif 687 | 688 | // wait until (HIGH == NDAC) 689 | if (Wait_level_on_pin(LOW,NDAC,htimeout)) { 690 | if (verbose) Serial.println(F("gpibWrite: timeout waiting NDAC")); 691 | digitalWrite(DAV, HIGH); 692 | return true; 693 | } 694 | #ifdef DEEP_DEBUG_BUILD 695 | Serial.print(" W4"); 696 | #endif 697 | 698 | digitalWrite(DAV, HIGH); 699 | set_dab(0); 700 | delayMicroseconds(10); 701 | #ifdef DEEP_DEBUG_BUILD 702 | Serial.println(" W5 - Done"); 703 | #endif 704 | 705 | return false; 706 | } 707 | 708 | /* 709 | Acceptor role. 710 | Read a single char on gpib managing the gpib 3wires handshake protocol with timeouts. 711 | See Intel AP-166; Figure2. 712 | Params: 713 | data is the address of the byte to put the read data in; 714 | EOS is the string containing all possible EOS characters (e.g. CR, LF, ...); An empty EOS means no use of end character.. 715 | 716 | Return values: 717 | 0 on success, 718 | 1 on last char, 719 | 2 on timeout. 720 | */ 721 | byte gpibRead(byte *data) { 722 | 723 | // prepare to for handshaking 724 | pinMode(NRFD, OUTPUT); 725 | pinMode(NDAC, OUTPUT); 726 | pinMode(EOI, INPUT_PULLUP); 727 | pinMode(DAV, INPUT_PULLUP); 728 | delayMicroseconds(10); 729 | 730 | // ensure NDAC is LOW as we left it last time we were called 731 | digitalWrite(NDAC, LOW); 732 | 733 | // Ready for more data 734 | digitalWrite(NRFD, HIGH); 735 | 736 | // wait until (LOW == DAV) /Talker has finished setting data lines.. 737 | if (Wait_level_on_pin(HIGH,DAV,htimeout)) { 738 | if (verbose) Serial.println(F("gpibRead: timeout waiting DAV")); 739 | return 2; 740 | } 741 | #ifdef DEEP_DEBUG_BUILD 742 | Serial.print("R1"); 743 | #endif 744 | 745 | // read from DIO 746 | *data = get_dab(); 747 | 748 | // NOT Ready for more data 749 | digitalWrite(NRFD, LOW); 750 | #ifdef DEEP_DEBUG_BUILD 751 | Serial.print(" R2"); 752 | #endif 753 | 754 | /* 755 | There are three ways to terminate data messages: 756 | - talker asserts the EOI (End or Identify) line, 757 | - talker sends an EOS (End of String) character, and 758 | - byte count. 759 | When you use more than one termination method at a time, all methods are logically ORed together.. 760 | Here we check for the first two only..(byte count not implemented yet). 761 | 762 | */ 763 | byte isEOI=0, isEOS=0; 764 | 765 | if (LOW == digitalRead(EOI)) { 766 | isEOI = 1; 767 | #ifdef DEEP_DEBUG_BUILD 768 | Serial.print(" -EOI-"); 769 | #endif 770 | } 771 | // eoi_only is a flag signaling that the high level ++read command has been issued with the "eoi" paramenter, 772 | // meaning that read must not check EOS and continue until EOI.. 773 | if (!eoi_only && strchr(EOSs, (char) *data)) { 774 | isEOS = 1; 775 | #ifdef DEEP_DEBUG_BUILD 776 | Serial.print("-EOS-"); 777 | #endif 778 | }; 779 | //end of termination management 780 | 781 | 782 | // data accepted 783 | digitalWrite(NDAC, HIGH); 784 | 785 | // wait until (HIGH == DAV) 786 | if (Wait_level_on_pin(LOW,DAV,htimeout)) { 787 | if (verbose) Serial.println(F("gpibRead: timeout waiting DAV")); 788 | return 2; 789 | } 790 | #ifdef DEEP_DEBUG_BUILD 791 | Serial.print(" R3-0x"); 792 | Serial.print(*data, HEX); 793 | Serial.print("("); 794 | Serial.print(isprint((char)*data) ? (char)*data : '\0') ; 795 | Serial.print(") -> "); 796 | #endif 797 | 798 | digitalWrite(NDAC, LOW); 799 | 800 | return byte (isEOI | isEOS); // ORed termination conditions 801 | } 802 | /* 803 | END OF LOW LEVEL ROUTINES 804 | */ 805 | 806 | ////////////////////////////////////////////////////////////////////////// 807 | 808 | /* 809 | FUNCTIONs (roles) ROUTINES 810 | */ 811 | 812 | /* 813 | Talker role. 814 | */ 815 | void gpibTalk(char *outbuf) { 816 | int i; 817 | 818 | #ifdef DEBUG_BUILD 819 | Serial.print(F("gpibTalk: called to send command: ")); Serial.print(outbuf); 820 | Serial.print(" @ addr = "); Serial.println(addr); 821 | #endif 822 | 823 | // prepare to talk 824 | pinMode(EOI, OUTPUT); 825 | // ensure EOI is not active 826 | digitalWrite(EOI, HIGH); 827 | 828 | if (set_comm_cntx(OUT)) { 829 | if (verbose) Serial.println(F("gpibTalk: set_comm-cntx failed.")); 830 | return; 831 | } 832 | 833 | // before sending the string to GPIB append the EOS to it 834 | 835 | #ifdef DEBUG_BUILD 836 | Serial.print("EOS=");Serial.println(eos); 837 | #endif 838 | switch (eos) { 839 | 840 | case 0: // appends CR+LF 841 | strcat(outbuf,EOSs); 842 | break; 843 | 844 | case 1: 845 | case 2: // appends CR or LF depending on eos value 846 | i=strlen(outbuf); 847 | outbuf[i]=EOSs[eos-1]; 848 | outbuf[i+1]=NULL; 849 | break; 850 | 851 | case 3: // do not append anything 852 | break; 853 | 854 | default: // eos MUST be 0,1,2,or 3. 855 | if (verbose) Serial.println(F("gpibTalk: ASSERT error: Invalid eos.")); //never reached.. 856 | return; 857 | }; 858 | 859 | // ok, ready to start sending DABs out.. 860 | // write outbuf but the last char 861 | while (0 != *(outbuf + 1)) { // char by char write, up to the last but one.. 862 | if (gpibWrite((byte)*outbuf)) { 863 | if (verbose) Serial.println(F("gpibTalk: gpib write failed @4.")); 864 | return; 865 | } 866 | delayMicroseconds(20); 867 | outbuf++; 868 | } 869 | 870 | // write last DAB (tipically the EOS..) 871 | if (eoi) digitalWrite(EOI, LOW); 872 | 873 | if (gpibWrite(*outbuf)) { 874 | if (verbose) Serial.println(F("gpibTalk: gpib write failed @5.")); 875 | return; 876 | } 877 | delayMicroseconds(20); 878 | 879 | digitalWrite(EOI, HIGH); // in any case deassert EOI 880 | 881 | if ( sendGPIB_Ucmd(UNL) ) { 882 | if (verbose) Serial.println(F("gpibTalk: sendGPIB_Ucmd failed.")); 883 | return; 884 | } 885 | } 886 | /* 887 | Listen role. 888 | Reads chars from GPIB an immediately send them to USB. 889 | Reads until char is signaled as last char, or a timeout occurred. 890 | */ 891 | boolean gpibReceive() { 892 | 893 | #ifdef DEBUG_BUILD 894 | Serial.print(F("gpibReceive: called.")); 895 | Serial.print(" From addr: "); Serial.println(addr); 896 | #endif 897 | 898 | if (set_comm_cntx(IN)) { 899 | if (verbose) Serial.println(F("gpibReceive: set_comm-cntx failed.")); 900 | return true; 901 | } 902 | 903 | // receive data (DABs) bytes 904 | byte c; 905 | boolean isLast; 906 | 907 | for (;;) { 908 | switch (gpibRead(&c)) 909 | { 910 | case 0: // everythink ok 911 | #ifdef DEEP_DEBUG_BUILD 912 | Serial.println((char)c); 913 | #else 914 | Serial.print((char)c); 915 | #endif 916 | continue; 917 | 918 | case 1: // everythink ok, but c has been signaled as the last char in the flow 919 | Serial.print((char)c); if (eot_enable) Serial.print((char)eot_char); 920 | break; 921 | 922 | case 2: // read timeout: usually device turned off or wrong addr 923 | if (verbose) Serial.println(F("gpibReceive: Read timedout.")); 924 | return true; 925 | break; 926 | 927 | default: 928 | if (verbose) Serial.println(F("gpibReceive: ASSERT error: gpibRead returned unexpected value.")); 929 | return true; // never reached 930 | break; 931 | } 932 | break; // breaks the for 933 | } 934 | 935 | if ( sendGPIB_Ucmd(UNT) ) { // UNTalk the device 936 | if (verbose) Serial.println(F("gpibReceive: sendGPIB_Ucmd failed.")); 937 | return true; 938 | 939 | } 940 | return false; 941 | } 942 | 943 | /* 944 | ************ GPIB COMMANDS SECTION 945 | */ 946 | 947 | // Uniline commands 948 | 949 | // toggle the IFC line 950 | void gpibIFC(void) { 951 | 952 | digitalWrite(IFC,LOW); 953 | delayMicroseconds(128); 954 | digitalWrite(IFC, HIGH); 955 | delayMicroseconds(20); 956 | } 957 | 958 | 959 | /* 960 | sends a Universal Multiline command to the GPIB BUS 961 | P.O.O.: assert ATN, gpibwrites the command, deassert ATN 962 | */ 963 | boolean sendGPIB_Ucmd(byte cmd) { 964 | // activate "attention" (puts the BUS in command mode) 965 | pinMode(ATN, OUTPUT); 966 | digitalWrite(ATN, LOW); 967 | delayMicroseconds(10); 968 | 969 | // issues the command 970 | if (gpibWrite(cmd)) { 971 | if (verbose) Serial.println(F("sendGPIB_Ucmd: gpib cmd write failed")); 972 | return FAIL; 973 | } 974 | delayMicroseconds(10); 975 | 976 | // deactivate "attention" (returns the BUS in data mode) 977 | digitalWrite(ATN, HIGH); 978 | delayMicroseconds(20); 979 | 980 | return SUCCESS; 981 | } 982 | 983 | 984 | /* 985 | sends a Addressed command to the GPIB BUS 986 | P.O.O.: assert ATN, address destination to listen, gpibwrites the command, unaddress all, deassert ATN 987 | */ 988 | boolean sendGPIB_Acmd(byte cmd) { 989 | // activate "attention" 990 | pinMode(ATN, OUTPUT); 991 | digitalWrite(ATN, LOW); 992 | delayMicroseconds(10); 993 | 994 | // send ADDRESS for device clear 995 | if (gpibWrite((byte)(0x20 + addr))) { 996 | if (verbose) Serial.println(F("gpibSDC: gpib ADRRESS write failed")); 997 | return FAIL; 998 | } 999 | delayMicroseconds(10); 1000 | 1001 | if (gpibWrite(cmd)) { 1002 | if (verbose) Serial.println(F("gpibSDC: gpib SDC (0x04) write failed")); 1003 | return FAIL; 1004 | } 1005 | delayMicroseconds(10); 1006 | 1007 | if (gpibWrite(UNL)) { 1008 | if (verbose) Serial.println(F("gpibSDC: gpib unlisten (UNL) write failed")); 1009 | return FAIL; 1010 | } 1011 | delayMicroseconds(10); 1012 | 1013 | // deactivate "attention" 1014 | digitalWrite(ATN, HIGH); 1015 | delayMicroseconds(20); 1016 | 1017 | return SUCCESS; 1018 | } 1019 | 1020 | /// 1021 | /// set Command context 1022 | /// command context is the environment of the requested communication: who talks, who speaks. 1023 | /// direction parameter: 1024 | /// IN = from the device to me (the controller); leaves the device in talk mode 1025 | /// OUT = from me (the controller) to the device; leaves the device in listen mode 1026 | /// 1027 | boolean set_comm_cntx(byte direction) { 1028 | #ifdef DEEP_DEBUG_BUILD 1029 | Serial.print(F("set_comm-cntx: direction:")); Serial.print(direction ? "IN" : "OUT"); 1030 | Serial.print(" remote addr="); Serial.println(addr); 1031 | #endif 1032 | 1033 | //attention 1034 | /* If the ATN line is asserted, any messages sent on the data lines are heard by all devices, 1035 | and they are understood to be addresses or command messages. 1036 | */ 1037 | digitalWrite(ATN, LOW); 1038 | delayMicroseconds(30); 1039 | 1040 | /* Bits 0 through 4 (5bits) indicate the primary address of the device, for which the Talker/Listener assignment is intended. 1041 | If bit 5 is high, the device should listen. 1042 | If bit 6 is high, the device should talk. 1043 | Bit 7 is a "don't care" bit. 1044 | addr = 1F (all ones) is reserved; it actually implements two commands: 1045 | when sent to listen (0x3F) implements UNL (unlisten) 1046 | when sent to talk (0x5F) implements UNT (untalk) 1047 | */ 1048 | // issue talker address 1049 | if (gpibWrite((byte)0x40 + (direction ? addr : MYADDR) )) { 1050 | if (verbose) Serial.println(F("set_comm_cntx: gpib write failed @1")); 1051 | return FAIL; 1052 | } 1053 | delayMicroseconds(20); 1054 | 1055 | // listener address 1056 | if (gpibWrite((byte)(0x20 + (direction ? MYADDR : addr) ))) { 1057 | if (verbose) Serial.println(F("set_comm_cntx: gpib write failed @2")); 1058 | return FAIL; 1059 | } 1060 | delayMicroseconds(20); 1061 | 1062 | // end of attention 1063 | digitalWrite(ATN, HIGH); 1064 | delayMicroseconds(20); 1065 | 1066 | return SUCCESS; 1067 | } 1068 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | (This document and the accompanying source code are copies of material 2 | originally made available [here][source].) 3 | 4 | [source]: http://egirland.blogspot.com/2014/03/arduino-uno-as-usb-to-gpib-controller.html 5 | 6 | # ARDUINO UNO as a USB to GPIB controller 7 | 8 | ## Disclaimer 9 | 10 | This program is provided as is. It is a hobby work. It doesn't work 11 | correctly. It has not been tested. It can damage your Arduino and the 12 | device you connect it to. Can have unexpected behaviors, can get 13 | stuck at any moment, can read and write data to/from the device in 14 | ways you might not expect. It can send wrong or erratic commands to 15 | the device. Can display data that differ from the actual data the 16 | device sent out. Can address GPIB devices differently from what you 17 | might expect. I have not conducted any speed test; the maximum speed 18 | supported is simply unknown. Is does not follow any official 19 | standard. Only a minimum set of the functions included in IEEE-488 is 20 | barely emulated. Hardware limitations: the lack of a GPIB line driver 21 | has two major implications: first: your Arduino is directly connected 22 | to the device GPIB port without any form of electrical protection of 23 | buffering - this can potentially damage your Arduino and/or your 24 | device; second: what can happen if you connect more than one GPIB 25 | device to the BUS is unpredictable both from an hardware and software 26 | point of view. I only experimented with a single GPIB device connected 27 | to the BUS. 28 | 29 | ## Introduction 30 | 31 | This project is aimed to provide a cheap and quick GPIB solution for those that need to gain control over a single instrument and interact with it (e.g. to dumo screen shots or calibrate instruments that can be calibrated via GPIB only -- HP6632A Power Supply being a good example). 32 | 33 | I supposed that cheap adapters were available on the market to interface GPIB instruments with a PC. I was wrong. The only feasible solution is to go with a Prologix USB to GPIB converter, still it would have cost me more than 100 EUR . 34 | Other solutions (open or proprietary) required buying or building hardware. 35 | 36 | On the other end I had an Arduino UNO floating around on my bench waiting for a problem to solve other than blinking leds or writing "hello world" on an LCD.. 37 | 38 | So I decided to face the challenge of writing down a sketch and have 39 | the Arduino play the adapter role I was looking for. To make a long 40 | story short.. (I supposed that GPIB was simple at least as much RS-232 41 | is, .. and it is not! I was wrong again!) after reading a lot of 42 | IEEE-488 documentation, I got a decent implementation of a GPIB 43 | controller out of my Arduino UNO. This post is dedicated to describe 44 | the project. 45 | 46 | ## Code availability 47 | 48 | I some of the feedbacks post in the HP or TEK Yahoo discussion groups 49 | I was accused of not having published the source code. Here it is. 50 | Enjoy it! BUT: I am interested in test instruments. I am not 51 | interested in developing open source projects. I wrote this program 52 | just because I am an ICT professional and consequently I have some 53 | educational reminiscence of programming. 54 | 55 | Because of the hard effort I put on the project, I kindly request you 56 | to donate a couple of bucks if you find this program useful for your 57 | hobby (see the [original page][source] for the donation link). 58 | 59 | --- 60 | 61 | This program allows to have the Arduino UNO to implement a GPIB 62 | controller. The objective was to have a way to get control of test 63 | instruments by the ability to send and receive commands to/from such 64 | devices via GPIB using a terminal emulator or an appropriate script. 65 | 66 | Despite the disclamer I can say that the objective has been 67 | successfully met; with this program uploaded to an Arduino UNO I can 68 | now create sessions with my hobby instruments and interact with them 69 | the way they allow too do (see instrument manual). Most of the 70 | version 1 limitations are still here. Actual limitations are: 71 | 72 | - the program operates in controller mode only; it is not (yet) able to behave like a device. 73 | - two important GPIB pins are not managed: REN and SRQ; 74 | - Serial poll is not implemented. 75 | - subaddressing is not implemented. 76 | - ++read [char] not implemented (useful?!); 77 | - ++rst, ++lon, ++savecfg, ++spoll, ++srq, ++status, ++help are not implemented; 78 | 79 | ## Hardware set up instructions: 80 | 81 | The hardware needed is simple: just a bunch of wires from the GPIB plug to Arduino pins. Here is the pin mapping: 82 | 83 | A0 GPIB 1 84 | A1 GPIB 2 85 | A2 GPIB 3 86 | A3 GPIB 4 87 | A4 GPIB 13 88 | A5 GPIB 14 89 | 4 GPIB 15 90 | 5 GPIB 16 91 | 12 GPIB 5 92 | 11 GPIB 6 93 | 10 GPIB 7 94 | 9 GPIB 8 95 | 8 GPIB 9 96 | 7 GPIB 11 97 | 98 | **NOTE**: GPIB pins 10, 17-24 goto GND, but (!!): 99 | 100 | - GPIB pin 17 (REN) has to be implemented ..stay ready to reroute it differently in the future; 101 | - GPIB pin 10 (SRQ) has to be implemented ..stay ready to reroute it differently in the future; 102 | - GPIB pin 12 should be connected to the cable shield (not used here - I left it n/c). 103 | 104 | Build the the shield the way you want. Nothing is simpler provided you 105 | have a minimum electronic construction skills. For testing I 106 | preferred the "flying" shield visible in the pictures but the actual 107 | construction is your choice. The male GPIB connector was realized 108 | sacrificing a CENTRONICS 32pins male connector out of a PC parallel 109 | printer cable. I have just cut it leaving 12 positions out of the 110 | original 16. 111 | 112 | #### Get it working 113 | 114 | Get the GPIB6.1.ino sketch compile it and upload it to the Arduino. 115 | If you want/need to see a bunch of debugging output, just uncomment 116 | the two #defines directives on top of the source file. 117 | 118 | Get any terminal emulator and manage to have it working with the FTDI 119 | RS232/USB converter (nothing to do under linux; install the FTDI 120 | driver in Windows: it cames for free with the Arduino IDE. I have 121 | successfully used the serial display provided by the arduino IDE, 122 | putty under Linux and HyperTerminal under (virtual) Windows XP. 123 | 124 | You probably have to set the some serial communication parameters. 125 | The default serial speed is 115200 bps. Other serial parameters are: 126 | 127 | - Data_bits=8 128 | - Stop_bits=1 129 | - Parity=None 130 | - Flow_control=None. 131 | 132 | I successfully also interacted with the controller using cat, tail -f, 133 | echo, .. linux commands redirected to the FTDI serial device 134 | (/dev/ttyACM0 in my case). To do that you have to play a little with 135 | the stty command. For sure you need to set -hupcl so that the DTR 232 136 | signal is not deasserted while disconnecting from the device. It can 137 | also be useful to defeat the "reboot on connection" feature of the 138 | Arduino. Google about these topics before doing it! You can make your 139 | arduino impossible to reprogram. 140 | 141 | The controller is silent at startup. To test it send a "++ver" command 142 | and the Arduino should reply with "ARDUINO GPIB firmware by E. 143 | Girlando Version 6.1". If you plan to interact directly with your 144 | instrument, please issue the command "++verbose". This will make your 145 | controller much more friendly (see below). 146 | 147 | ## Principle of operations 148 | 149 | Everything received from the USB is passed to GPIB (126 char max). 150 | 151 | Characters from the USB are fetched in line by line and buffered. The software support for both CR, LF and CRLF line termination. 152 | 153 | Once the line termination char is encountered in the USB input stream 154 | the entire buffer gets parsed. If you need to include CR or LF in the 155 | input stream to be sent to your instrument you must rely on the help 156 | of the linux serial device driver using ^V in front of ^M or ^J to 157 | defeat their default behaviour of terminating the command shell 158 | lines.. (see stty man page). Still the ^V mechanism is not enough. ^V 159 | prevents the linux driver from sending out your line in the middle of 160 | your editing, but still the received CR or LF are interpreted as end 161 | of line by the Arduino. To avoid this an escape mechanism has been 162 | implemented. The escape char is "ESC" or 0x1B. So to send CR to the 163 | GPIB BUS you need to type "ESC^V^M" and similarly for LF. To send a 164 | single "ESC" you must escape it with "ESC" so you need to type "ESC" 165 | two times. You can terminate the input line with "" so that the 166 | following CR or LF will be escaped. If you insert unescaped CR or LF 167 | in the stream (e.g typing "^V^M" with no preceding "ESC") the line is 168 | split in two parts: everything preceding the CR or LF is passed on for 169 | processing; everything following the CR or LF is simply discarded. 170 | I don't know of any way to send CR or LF or ESC ascii char from the Arduino IDE serial monitor. Testing for all this stuff has been conducted via putty. 171 | 172 | **WARNING**: if you use buffered I/O on the sender side, and you often do, 173 | be aware that sending more than 64 chars per line can lead to serial 174 | buffer overflow as the Arduino sketch cannot keep pace with the burst 175 | of characters coming in @115200bps. Yet, the program detects this 176 | situation, issues a "Serial buffer overflow - line discarded. Try 177 | setting non buffered I/O" error message and discards the line 178 | entirely. As the error message suggests, you can switch your sender 179 | (terminal emulator) to unbuffered I/O so that it sends out one char at 180 | a time. If you are typing at a keyboard the problem is solved (unless 181 | you are typing at the speed of light); if the data comes from some 182 | kind of script, care must be taken to send small chunks of data 183 | introducing delays to allow for the program to eat them. 184 | 185 | Line starting with "++" are assumed to be commands directed to the 186 | controller and are parsed as such. Nothing beginning with "++" can be 187 | sent to instruments. Everything not starting with "++" is sent to the 188 | addressed instrument over the GPIB BUS. 189 | 190 | ## Implemented commands 191 | 192 | **NOTE**: the command interpreter is very raw: I remember from high school that they have to be programmed in a certain way, but I don't really remember how; so I did my best. Misspelled commands will (better: should) generate a Syntax error message. As soon as a non existent command code or a Syntax error is detected the line is discarded entirely. After a valid command has been recognized, any additional characters following it (if any) is discarded. 193 | 194 | 195 | - **++addr [address]** 196 | Fully compatible with the "++" de facto standard, but subaddressing is not implemented (SADs are syntactically accepted but ignored). 197 | 198 | - **++ver** 199 | just displays a version string of the controller. 200 | 201 | - **++auto** 202 | Fully compatible with the "++" de facto standard. 203 | But what is automode? Ok, you have to know that GPIB devices do not work the way we are used to with modern devices. When you send a command they process it and if any output is generated they put it to an instrnal buffer waiting to be addressed to speak. The final result is that just to get the identification string out of a device you have to send the request command (e.g "id?") and then issue the command "++read" to the controller. Automode ON just makes this for you: for every string sent to GPIB, the controller issues an implicit "++read" to the addressed device. This gives you the impression to have the device immediately react in front of the string you sent it. Beautiful! ...however a hidden trap is around: some GPIB device gets upset if asked to speak when it has nothing to say and with automode ON this can happen very often (I have an instrument that lights the error annunciator on the front panel when this situation occurs, but yet it continues working as nothing happened). 204 | For this reason automode is blocked if you just send an empty string pressing CR or NL. Automode is also suppressed with "++" commands. 205 | 206 | - **++clr** 207 | Fully compatible with the "++" de facto standard. 208 | 209 | - **++eoi** 210 | Fully compatible with the "++" de facto standard. 211 | 212 | - **++eos** 213 | Fully compatible with the "++" de facto standard. 214 | 215 | - **++eot_enable** 216 | Fully compatible with the "++" de facto standard. 217 | 218 | - **++eot_char** 219 | Fully compatible with the "++" de facto standard. 220 | 221 | - **++ifc** 222 | Fully compatible with the "++" de facto standard. 223 | 224 | - **++llo** 225 | Fully compatible with the "++" de facto standard. 226 | 227 | - **++loc** 228 | Fully compatible with the "++" de facto standard. 229 | 230 | - **++mode** 231 | the command is syntactically fully compatible, but the only mode implemented is CONTROLLER; so the cammand is provided for compatibility but it is inoperative. 232 | 233 | 234 | - **++read** 235 | Fully compatible with the "++" de facto standard, but ++read [char] is not implemented. 236 | 237 | - **++read_tmo_ms** 238 | Fully compatible with the "++" de facto standard. 239 | 240 | - **++trg** 241 | is implemented for the addressed device only; it doesn't accept PADs or SADs paramenters. 242 | 243 | 244 | - **++rst, ++lon, ++savecfg, ++spoll, ++srq, ++status, ++help** are not implemented. 245 | 246 | Two additional proprietary commands (not usually included in the "++" set) have been implemented: 247 | 248 | - **++verbose** 249 | This command toggles the controller between verbose and silent mode. When in verbose mode the controller assumes an human is working on the session: a ">" prompt is issued to the USB side of the session every time the controller is ready to accept a new command and most of the "++" commands now print a useful feedback answers (i.g. the new set value or error messages). 250 | This breaks the full "++" compatibility. Do not use verbose mode when trying to use third party software. Default is verbose OFF. 251 | This is very useful to see timeout errors otherwise hidden.. 252 | 253 | - **++dcl** 254 | Sends an unaddressed DCL (Universal Device Clear) command to the GPIB. It is a message to the instrument rather than to its interface. So it is left to the instrument how to react on a DCL. Typically it generates a power on reset. See instrument documentation. 255 | 256 | ## Feedbacks 257 | 258 | This version can be successfully used to interact with your 259 | instruments, but is still imperfect. Please post feedbacks and 260 | comments below this post. In addiction to bug fixing, I am interested 261 | in sharing experiences in using third party software especially for 262 | dumping plots down from instruments. I have a TEK2232 and an HP853 I 263 | am interested in producing plots from. 264 | 265 | ## Future development 266 | 267 | I am going to improve it by bug fixing and keep the documentation deep and updated and working on Version 6.2 that will wide the project scope up to: 268 | 269 | - settings save/restore, 270 | - serial poll support (if not too difficult), 271 | - improve ++trg compatibility, 272 | - porting to different Arduino platforms. 273 | 274 | ## Conclusions 275 | 276 | Enjoy you GPIB controller and please do not forget to donate some buck just to recognize the effort I made to develop and document this project. 277 | Thank you! 278 | Emanuele. 279 | 280 | ## License 281 | 282 | ![Creative Commons License][3] 283 | Arduino USB to GPIB firmware by [E. Girlando][4] is licensed under a [Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License][5]. 284 | Permissions beyond the scope of this license may be available at [mailto:emanuele_girlando@yahoo.com][6]. 285 | 286 | [1]: https://3.bp.blogspot.com/-wE96mGo60aw/UxO-URJXFfI/AAAAAAAAALY/rn5Y8xF-vSI/s1600/DSCN0065.JPG "TEK2232 loves Arduino" 287 | [2]: https://1.bp.blogspot.com/-39JcmqnoDZY/UxO-VGooaJI/AAAAAAAAALg/8OZBcEPSPoo/s1600/DSCN0066.JPG "Fluke PM2534 loves Arduino" 288 | [3]: http://i.creativecommons.org/l/by-nc-nd/4.0/88x31.png 289 | [4]: http://egirland.blogspot.it/ 290 | [5]: http://creativecommons.org/licenses/by-nc-nd/4.0/ 291 | [6]: mailto:emanuele_girlando@yahoo.com 292 | [7]: http://egirland.blogspot.com/2014/02/arduino-uno-as-usb-to-gpib-adapter.html 293 | [8]: https://3.bp.blogspot.com/-peAxLPToKwE/UvH1nqcAogI/AAAAAAAAAKk/QvRzaeImYaU/s1600/DSCN0057.JPG "GPIB-Arduino fixture" 294 | --------------------------------------------------------------------------------