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
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:
- Put data on bus (data pins 2 through 8)
- Drive pin 1 low
- Wait for the StarPort to drive pin 11 high
- Drive pin 1 high
- 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:
- Tristate the data lines (set bit 5 of the control register high)
- Drive pin 14 low
- Wait for the StarPort to drive pin 11 high
- Read data off the data bus
- Drive pin 14 high
- 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
|