Dove Systems
Home |  Products |  Pricing |  Dealers |  Free Stuff! |  Related |  PDF Files |  Support |  Contact

Talking to the Stars - Part 2

Revised 12 December 2001

This document discusses the parallel printer port protocol used to communicate with the Dove Systems StarPort. This is the second revision of the parallel port protocol. StarPorts with this revision have green LEDs, while StarPorts with the first revision (as described in part 1) have red LEDs. Users can use this information to write software that takes advantage of the StarPort's DMX transmit and receive capabilities. This version of the StarPort requires the parallel port be in byte, bidirectional, or PS/2 mode if data is to be read from the StarPort. There is generally a BIOS setup setting where one of these modes may be selected. If data is only going to be sent to the StarPort by the host computer, the port should be set to SPP (Standard Parallel Port) mode.

The StarPort receives a DMX signal and places the data in 512 bytes of an internal RAM buffer. Application software can read this buffer through the standard printer port "byte mode." The StarPort continuously transmits 512 bytes of DMX data (plus framing and start code of 0x00). The data to be transmitted is contained in another 512 bytes of the internal RAM buffer. The entire RAM buffer is available to the applications program, though a typical application would read the received data buffer and write to the transmit buffer. Independent read and write data pointers are autoincremented after each read or write. A typical applications program sets the write pointer to the bottom of the transmit buffer space, then sends each DMX channel's data to the StarPort. The application would then set the read pointer to the bottom of the receive buffer space, then read each channel's DMX data in sequence.

Write Handshake

Click here for full size image

The StarPort has a single handshake line that acknowledges both read and write. When the host drives the (active low) write strobe (pin 1) low, the StarPort drives pin 11 (SPP mode BUSY) high. When the host drives the write strobe high, the StarPort drives pin 11 low. The host computer should verify the StarPort is following along each step of the way. A suggested process would be:

  1. Put data on bus (data pins 2 through 8)
  2. Drive pin 1 low
  3. Wait for the StarPort to drive pin 11 high
  4. Drive pin 1 high
  5. Wait for the StarPort to drive pin 11 low
Typical handshake timing is shown in figure 1.

Read Handshake

The read handshake is almost identical to the write handshake. However, instead of driving the write strobe (pin 1) low, the read strobe (pin 14) is driven low. As in writing, the StarPort acknowledges pin 14 being low by driving pin 11 high. When the host drives pin 14 high, pin 11 goes low. The timing of a read handshake is identical to that of the write handshake. A typical handshake process would be:

  1. Tristate the data lines (set bit 5 of the control register high)
  2. Drive pin 14 low
  3. Wait for the StarPort to drive pin 11 high
  4. Read data off the data bus
  5. Drive pin 14 high
  6. Wait for the StarPort to drive pin 11 low

Interrupts

The StarPort can be used in a polled or interrupt mode. As an aid to Windows programmers, the StarPort can generate a 30 Hz interrupt. This interrupt is typically used to "dump a buffer" to the StarPort 30 times a second. The printer port generates an interrupt on a nAck when bit 4 of the control register is set. The StarPort continuously drives pin 10 (nAck) with a 30 Hz square wave. Setting bit 4 of the printer port control register will cause an interrupt every 33ms. The interrupt service routine can then do a "buffer exchange" with the StarPort.

The Parallel Port Control and Status Lines

The table below summarizes the control and status lines of the parallel port. Each line is identified by the pin number on the DB25 connector. The Control or Status register bit number is identified. A minus in front of the C or S indicates the bit is inverted. Writing a 1 to that bit of the control register results in the corresponding pin going low. Similarly, for -S7 (bit 7 of the status register), the register bit will read 0 if the line is high. The table identifies the SPP and Byte Mode functions of the pins. The actual handshake used in the StarPort is as described above.

Pin Register Bit SPP Mode Byte Mode
1 -C0 -Strobe: Host pulses low so periph captures data HostClk: Pulsed low by host after setting HostBusy high
14 -C1 HostBusy: Host drives low to indicate it's ready for data. Host drives high on capturing data (after periph drives S6 low)
17 -C3 -SelectIn: Host drives low to say it wants to communicate 1284Active: High indicates byte mode, low indicates SPP mode
15 S3 -Error: Driven high by periph -DataAvail: Periph drives high if it has no more data
13 S4 Select: Periph drives high in response to -C3 low XFlag: "same as previous negotiation"
12 S5 PaperEnd: Periph drives high if out of paper AckDataReq: Follows S3
10 S6 -ACK: Periph pulses low to ack receipt of data PtrClk: Periph drives low after putting data on bus
11 -S7 Busy: High if periph busy PtrBusy: Periph drives high if busy
Table 1 - Parallel port register bits

Write Data

Data is written to the StarPort using the write handshake described above. Note that a write of a 01 is considered to be the prefix to a StarPort command. If the data to be written to a particular location is to be a 01, send the 01 twice.

StarPort Commands

The StarPort has three commands. These are write 01, set READ pointer, and set WRITE pointer. Each command is initiated by sending a 01 to the StarPort. The command consists of the next byte or two sent to the StarPort.

Write 01

As mentioned in Write Data, above, a 01 is interpreted as a command prefix. A 01 followed by another 01 is interpreted as a command to write 01 to the current write location, then increment the write pointer. Applications software can include a 01 check as data is sent to the StarPort, sending any 01 data twice.

Set Read Pointer

The read pointer points to the next data to be read from the StarPort (in byte mode). The DMX receive buffer is in locations 0 to 511 (0x00 to 0x1ff) of the StarPort RAM, with DMX channel 1 landing in location 0. Unused channels are set to zero. To set the Read Pointer, send the 01 command prefix followed by 0x40+bbb (where bbb is address bits A10 through A8), followed by another byte representing A7 through A0. To set the StarPort read pointer to the bottom of the receive buffer space (pointing at received DMX channel 1), send the byte sequence: 0x01, 0x40, 0x00.

Set Write Pointer

The write pointer points to the next data location to be filled by a write. The DMX transmit buffer is in locations 512 to 1023 (0x200 to 0x3ff) of the StarPort RAM, with DMX channel 1 landing in location 512 (0x200). At start up, all RAM locations are cleared, so DMX channels not set by the applications program will be transmitted as 00. Note that the StarPort always transmits 512 DMX channels. To set the Write Pointer, send the 01 command prefix followed by 0x80+bbb (where bbb is address bits A10 through A8), followed by another byte representing A7 through A0. To set the StarPort write pointer to the bottom of the transmit buffer space (pointing at transmit DMX channel 1), send the byte sequence 0x01, 0x82, 0x00.

Printer Port Base Addresses

On IBM compatible computers, the BIOS sets up a table of 16 bit I/O addresses for printers LPT1 through LPT3. The contents of this table are listed in table 2. Your code can peek these locations to determine the actual base I/O address of the parallel ports, since they may vary system to system.

Memory Address Contents
0:0x408 Low half of LPT1 I/O address
0:0x409 High half of LPT1 I/O address
0:0x40a Low half of LPT2 I/O address
0:0x40b High half of LPT2 I/O address
0:0x40c Low half of LPT3 I/O address
0:0x40d High half of LPT3 I/O address
Table 2 - Printer Port Address Table

Printer Port Registers

Each printer port has three registers associated with it. These are at the base address+0, base address+1, and base address+2. Table 3 lists the contents of each of these locations. Table 4 lists the pin out of the parallel port DB25 connector. Most pins have a particular register bit associated with them. A plus sign in front of the register bit designation indicates the pin is active high. Writing a 1 to a write register will result in a high on that pin. Putting a high logic level on an input pin will result in a 1 in the appropriate status register bit. A minus sign in front of a register bit designation indicates that the pin is active low. A 1 in a register bit represents a logic low on the pin. Data pins are represented by D. Status pins (inputs) are represented by S, while control pins (outputs) are represented by C. Finally, the number in a register bit designation corresponds to the bit position in the register. A 7 is the most significant bit, while a 0 is the least significant bit.

Register Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
0 - Bidirectional Data Data bit 7 - DB25-2 Data bit 6 - DB25-3 Data bit 5 - DB25-4 Data bit 4 - DB25-5 Data bit 3 - DB25-6 Data bit 2 - DB25-7 Data bit 1 - DB25-8 Data bit 0 - DB25-9
1 - Read Only Status -S7: Busy, DB25-11 S6: -Ack, DB25-10 S5: PaperEnd, DB25-12 S4: Select, DB25-13 S3: -Error, DB25-15 S2: unused S1: unused S0: timeout
2 - Write Only Control C7: unused C6: unused C5: HostRead/-HostWrite C4: IRQ enable (-Ack causes IRQ) -C3: -SelectIn, drive PIN low when writing to StarPort, drive PIN high when reading from StarPort, DB25-17 C2: -Init, DB25-16 -C1: -AutoLF, pulse PIN low to read from StarPort, DB25-14 -C0: -Strobe, pulse PIN low to write to StarPort, DB25-1
Table 3 - Parallel port registers

Sample Code

Sample code for the StarPort appears below. This is the actual interface code used in the DOS version of the StarPort software. Note that this version uses high numbered DMX input channels for an optional submaster console. You can skip some of the states in the HandleDongle state machine if you don't want to handle possible gaps in received data channels. Note also that HandleDongle is passed the parallel port number to use, the number of channels to transmit, the number of channels to receive, and whether this DMX submaster panel is present. The StarPort always transmits 512 channels. The nTx and nRx parameters determine how many channels of data are exchanged between the host and the StarPort.

dongle.c


void SendToDongle(data,PortAddr)
// Note that the printer port inverts both C0 and S7. Since the StarPort
// expects low strobes, we set bit C0 high to create the low strobe. The
// StarPort then drives the Busy line high, which makes S7 low. We wait
// for S7 to go low. We then drive the strobe high by clearing C0. The
// StarPort responds by driving Busy low (S7 high). We wait for S7 to
// go high.
// -C0-----------------------___________------------
// -S7_____________________________-----------------__


  unsigned PortAddr;       // base address of the lpt port we're using.
  {
    _outb(data,PortAddr);       // Put data on the bus
    _outb(0x01,PortAddr+2);     // Set -c0 
    do {
    } while ((_inb(PortAddr+1) & 0x80)!=0); // Loop while S7 high (busy low) 
    _outb(0x00,PortAddr+2);     // Set -c0 high 
    do {
    } while ((_inb(PortAddr+1) & 0x80)==0); // Loop while Sy low (busy high)
  }

char ReadFromDongle(PortAddr)
// Reads a byte from the dongle. We'll also use S7
// for handshake.  Timing is:

// -C1-----------------------___________------------
// -S7_____________________________-----------------__


  unsigned PortAddr;       // base address of the lpt port we're using.
  {
    char RxByte;        // The data we pulled off the port
//    _outb(0x20,PortAddr+2);     // Set bit 5 high (host datalines tristated)
    _outb(0x22,PortAddr+2);     // Set -c1 low causing dongle to put data on
                                // data lines and put port in input mode (bit
                                // 5 high). 
    do {
    } while ((_inb(PortAddr+1) & 0x80)!=0); // Loop while S7 high (busy low)                                // indicating StarPort got our strobe 
    RxByte=_inb(PortAddr);      // Read the data
    _outb(0x20,PortAddr+2);     // Tell dongle to take data off bus but leave
                                // lpt port data line drivers off. Turning on
                                // the host drivers right now would give
                                // contention since it takes time for the
                                // StarPort to detect the high strobe. We
                                // enable the host drivers (if needed) in
                                // SendToDongle after the StarPort is ready
                                // and taken its drivers off line.
    do {
    } while ((_inb(PortAddr+1) & 0x80)==0); //Loop while S7 low (Busy high) 
    return (RxByte);            // Return the data we got!
  }

void ResetDongleWritePointer(PortAddr)
// Sends 0x01 0x82 0x00 to Dongle, setting write pointer to 0x200, which
// corresponds to DMX channel 1 in the transmit buffer. hh 5/3/01
//  char lpt;             // Printer port to use (1 based)
  unsigned PortAddr;
  {
      SendToDongle(0x01, PortAddr);
      SendToDongle(0x82, PortAddr);
      SendToDongle(0x00, PortAddr);
  }

void ResetDongleReadPointer(PortAddr)
// Sends 0x01 0x40 0x00 to Dongle, setting read pointer to 0x00, which
// corresponds to DMX channel 1 in the receive buffer. hh 5/3/01
//  char lpt;
    unsigned PortAddr;
  {
      SendToDongle(0x01, PortAddr);
      SendToDongle(0x40, PortAddr);
      SendToDongle(0x00, PortAddr);
  }
void DongleReadPointerToSubs(PortAddr)
// Send 0x01 0x41 0xc0 to Dongle, setting read pointer to 0x1c0 (448 dec)
// or the start of the DMX sumbasters on incoming DMX.
//  char lpt;
    unsigned PortAddr;
  {
      SendToDongle(0x01, PortAddr);
      SendToDongle(0x41, PortAddr);
      SendToDongle(0xc0, PortAddr);
  }


char SubMaster[64];     // Declare dongle submaster array 0..63
                        // This array will be loaded with received
                        // DMX channels 449..512
char SubMasterIndex;    // Index used by GetDongleSub

int GetDongleSub(void)
  {                     // Return value from SubMaster array, bump pointer.
    return(SubMaster[SubMasterIndex++]);
  }

void ResetSubMaster(void)
  {
    SubMasterIndex=0;
  }

void handleDongle(DonglePort, nDimmers, nRx, DmxSub, in_buffer, out_buffer)
  char DonglePort;      /* Printer port dongle on (1 based)*/
  int  nDimmers;        /* Number of dimmers      */
  char nRx;             // Number of receive channels
  char DmxSub;          // 0xff if we're reading submasters
  char *in_buffer;      /* Segment where input buffer starts */
  char *out_buffer;     /* Segment where output buffer starts */
// Transmit and receive a byte from the dongle.
{
  static char State=0;
  static char *NextChan=0;         // Could use int, but peek/poke expect ptr
  static unsigned PortAddr;     // hardware I/O address of port (used to read from dongle)
  char TxData;                    /* byte we're about to send */
  static char RxData;                  /* byte we are receiving    */
  switch(State)
    {
      case 0:                   // Find port address
        {
          PortAddr=256*_peek(0x409+2*(DonglePort-1),0)+_peek(0x408+2*(DonglePort-1),0);
                                // Set PortAddr according to BIOS RAM data
          State++;              // Do next state next time
          break;
        }
      case 1:
        {
          NextChan=0;             // Point to first chan in host buffer
          State++;                 // On to next state
          break;
        }
      case 2:
        {
          ResetDongleWritePointer(PortAddr); // StarPort points to bottom of out buffer ram
          State++;              // Do next state next time
          break;
         }
      case 3:                   // Send a byte of data
        {
          TxData=_peek(NextChan+1,out_buffer);          // Get byte from buffer
          SendToDongle(TxData,PortAddr);                // and send it
          if (TxData==0x01){
              SendToDongle(TxData,PortAddr);           // If it was 1, send again
          }
          NextChan++;                                   // Point to next chan in Prostar buffer
          if (NextChan>nDimmers){                       // Sent last channel
            if (nRx>0){                                 // Receiving anything?
              State=4;}                                 // Yes, go do it
            else{
              if (DmxSub){
                State=7;}
              else {
                State=1;}                                // No, go back to putting stuff in dongle
            }
          }
          break;
        } //end case 3
      case 4:                    // Set ReadPointer address
        {
          ResetDongleReadPointer(PortAddr);               // Point to bottom of rx buf
          NextChan=0;
          State++;
          break;
        }
      case 5:                   // Get read bytes
        {
          _poke(ReadFromDongle(PortAddr),NextChan+1,in_buffer);
                                 // Get byte from dongle and put in buffer
          NextChan++;            // Point to next channel to receive
          if (NextChan>=nRx){    // Received all channels?
            if (DmxSub){
              State=6;}          // If we have DMX submasters, move on to state 4
            else{
              State=1;           // Go back to sending data from start
            }
          } // endif
          break;
        } // end case 5
      case 6:                   // Point to subs
        {
          NextChan=0;           // Index into subs array
          DongleReadPointerToSubs(PortAddr);
          State++;
          break;
        }
      case 7:                     // Get byte from subs
        {
          SubMaster[NextChan++]=ReadFromDongle(PortAddr); // Get the data into array
          if (NextChan>64){
            State=1;            // We've got all the subs. Go back to getting
          }                     // channel data from start
          break;
        } // end case 
    } // end switch
} // end handledongle



More Sample Code

The code below is written in Borland C++. It implements a bargraph display using the StarPort as the DMX input. Dongle.cpp allocates a transmit and receive buffer. Calls to HandleDongle exercise the state machine that handles parallel port I/O to the StarPort. Calls to get and put get data and put data in the buffers.





dongle.cpp

// Dongle.cpp // This module defines the dongle class. It includes transmit and receive // arrays, initialization code, data access functions, and a "handle dongle" // procedure that is called as often as possible (simulating multitasking // through polling). // Revised 9/4/97 - hh // Revised 9/10/01 - Revised to use SPP and byte modes. hh // Revised 12/11/01 to use simple handshake. hh #include // Get definition of peek() class dongle { protected: // Data accessible only to this and derived classes unsigned char TxArray[512]; // Data to be transmitted unsigned char RxArray[512]; // Received data int MaxTx, MaxRx; // Highest channel number to be tx or rx char Submaster; // nonzero if we have a submaster console char PortNum; // The parallel port (1..3) being used unsigned int PortAddress; // Address of printer port unsigned char state; // State of HandleDongle void SendToDongle(char data); // Output data and pulse strobe void ResetDongleWritePointer(); // Reset the write pointer in StarPort void ResetDongleReadPointer(); // Reset the read pointer in StarPort void DongleReadPointerToSubs(); // Point read pointer to submaster area char ReadFromDongle(); // Read from StarPort public: dongle(unsigned char port, int MxTx, int MxRx, char Sbmaster); // Constructor void HandleDongle(void); // Poll dongle void put(int Chan, unsigned char Value) { TxArray[Chan-1]=Value; // Store value from 1 base to 0 base array } unsigned char get(int Chan) { return(RxArray[Chan-1]); // Return received value } }; // End of class definition dongle::dongle(unsigned char port=0, int MxTx=0, int MxRx=0, char Sbmaster=0) // Constructor. Pass LPT port, highest channel to transmit, highest // channel to receive, and whether a submaster console is present. If // submaster is present, it can be pulled from channels 449..512. { int n; MaxTx=MxTx; // Set MaxTx, MaxRx, and Submaster based on MaxRx=MxRx; // arguments. Submaster=Sbmaster; PortNum=port; // LPT port being used for (n=0; n<512; n++) { TxArray[n]=0; // Clear the tx array RxArray[n]=0; // and the Rx array }; switch(port) // Set up PortAddress { case 1: // LPT1 { PortAddress=peek(0,0x408); // Note 16 bit peek! break; } case 2: // LPT2 { PortAddress=peek(0,0x40a); break; } case 3: // LPT3 { PortAddress=peek(0,0x40c); break; } } // end switch(port) } // End of dongle:dongle constructor void dongle::SendToDongle(char data)// Output data and pulse strobe { outportb(PortAddress, data); // Put data on bus outportb(PortAddress+2, 0x1); // Set -C0 low do { } while ((inportb(PortAddress+1) & 0x80) !=0); // Loop while S7 high (busy low) outportb(PortAddress+2, 0x00); // Set -co high do { } while ((inportb(PortAddress+1) & 0x80)==0); // Loop while S7 low (busy high) } void dongle::ResetDongleWritePointer() // Sends 0x01 0x82 0x00 to Dongle, setting write pointer to 0x200, which // corresponds to DMX channel 1 in the transmit buffer. hh 5/3/01 { SendToDongle(0x01); SendToDongle(0x82); SendToDongle(0x00); } void dongle::ResetDongleReadPointer() // Sends 0x01 0x40 0x00 to Dongle, setting read pointer to 0x00, which // corresponds to DMX channel 1 in the receive buffer. hh 5/3/01 { SendToDongle(0x01); SendToDongle(0x40); SendToDongle(0x00); } void dongle::DongleReadPointerToSubs() // Send 0x01 0x41 0xc0 to Dongle, setting read pointer to 0x1c0 (448 dec) // or the start of the DMX sumbasters on incoming DMX. { SendToDongle(0x01); SendToDongle(0x41); SendToDongle(0xc0); } char dongle::ReadFromDongle() // Reads a byte from the dongle. Uses S7 for handshake. Note that both C0 and // S7 are inverted by the printer port. // -C1 (strobe)-----------------------___________------------ // S7 (busy)_____________________________-----------------__ { char RxByte; // The data we pulled off the port outportb(PortAddress+2, 0x22); // Set -c1 low causing dongle to put data on // data lines and put port in input mode (bit // 5 high). do { } while ((inportb(PortAddress+1) & 0x80)!=0); // Loop while S7 high (busy low) RxByte=inportb(PortAddress); // Read the data outportb(PortAddress+2, 0x20); // Tell dongle to take data off bus but leave // lpt port data line drivers off. Turning on // the host dirvers right now would give // contention since it takes time for the // StarPort to detect the high strobe. We // enable the host drivers (if needed) in // SendToDongle after the StarPort is ready // and taken its drivers off line. do { } while ((inportb(PortAddress+1) & 0x80)==0); // Loop while S7 low (busy high) return (RxByte); // Return the data we got! } void dongle::HandleDongle(void) // Poll the dongle and do what's necessary. { static int NextChan; static char State; switch(State) { case 0: { ResetDongleWritePointer(); // Set write pointer to start NextChan=0; // Array index State++; // Do next state next time break; } case 1: // Send a byte of data { SendToDongle(TxArray[NextChan]); // Send it if (TxArray[NextChan]==0x01){ SendToDongle(0x01); // If it was 1, send again } NextChan++; // Point to next chan if (NextChan>MaxTx){ // Sent last channel if (MaxRx>0){ // Receiving anything? State=2;} // Yes, go do it else{ if (Submaster){ State=4;} else { State=0;} // No, go back to putting stuff in dongle } } break; } //end case case 2: // Set ReadPointer address { ResetDongleReadPointer(); // Point to first read location in dongle NextChan=0; State++; break; } case 3: { RxArray[NextChan]=ReadFromDongle(); // Get byte from dongle and put in buffer NextChan++; // Point to next channel to receive if (NextChan>=MaxRx){ // Received all channels? if (Submaster){ State=4;} // If we have DMX submasters, move on to state 4 else{ State=0; // Go back to sending data from start } } // endif break; } // end case 3 case 4: // Point to subs { NextChan=448; // Index into subs area of RxArray DongleReadPointerToSubs(); // Point to bottom of subs State++; break; } case 5: { RxArray[NextChan++]=ReadFromDongle(); // Get the data into array if (NextChan>512){ State=0; // We've got all the subs. Go back to getting } // channel data from start break; } // end case } // end switch } // end handledongle
Home |  Products |  Pricing |  Dealers |  Free Stuff! |  Related |  PDF Files |  Support |  Contact
www.dovesystems.com