Skip to main content

PCStage interface



Well, I know I'm late to the arduino party but I wanted to replace the old 64 channel PCStage printerport interface I made with a PIC many years ago.



I could have gone  for a newer type of PIC with more memory but arduino boards are so cheap I could not resist to try one out. Also there is no need to reinvent the wheel, a quick googling revealed a few DMX libraries.

PCStage output

The output format of the software is really very simple.
 8 bit channel values are clocked out after a start signal.

pin 1: clock
pin 2 to 9: data
pin 14: start

That is the good news, the bad news is that by observing the scope we can see there is not much time to get the data.



Fitting in the Arduino

There is another complication, the DMX libraries use the serial port. Very clever but that means there is no byte wide port available on the arduino UNO to capture the data. Pin 0 and 1of PORTD are used by the UART. I solved this by connecting D0 and D1 to PORTB 0 and 1, D2 to D7 connect to PORTD.
After masking out the relevant bits the two port registers can be ORed.


As we can see in the schematic the only additional needed component is a RS485 line driver.


With a processor running at 16MHz there are a few things that are ruled out. The bottleneck is following the clock pulses. We need to test the status then combine the two port registers and put the result in a buffer. This rules out interrupts and  also the arduino  digitalRead function. Also function calls are to be avoided.

I found a library that allows to fill the buffer array without a call here.
After reading a bit it became clear that very little code would be needed to complete the program.

Setting up the library

First in the header file (lib_dmx.h) find line 43 and comment out the unused UARTs like so:

// *** comment UARTs not used ***
#define        USE_UART0
//#define        USE_UART1
//#define        USE_UART2
//#define        USE_UART3

No other modifications in the library files are needed.

To initialize following commands are needed:

 ArduinoDmx0.set_tx_address(1);
 ArduinoDmx0.set_tx_channels(MAXCHANNELS);

 ArduinoDmx0.set_control_pin(-1);
 ArduinoDmx0.attachTXInterrupt(rdlpt);
 ArduinoDmx0.init_tx(DMX512);

In human language, the start address and the number of channels. The controlpin on the RS485 chip is not used here, set to -1.
After every packet the library can call a function, Here we provide the name of that function. This is where we will read the data.
The init call starts up the transmission of the buffer.


Capturing data

We read the parallel port in the 'rdlpt' function. There is nothing difficult about this. The trick is to get the speed.
First wait for the start bit to go low, then wait for the start bit to go high again. This will skip the start code. The library takes care of the start code, we do not  need to provide it, it is 0 anyway.
Now we set up a for loop. In the loop we wait for the clock to go low, then we compose the byte and put it in the buffer. For safety we now wait for the clock to go high again. While waiting for the clock to go low we count and when nothing happens we reset the buffer size to the actual value and break the loop.

When the function exits transmission resumes.

The compiler does a great job, I set pin13 after detecting the clock and reset after updating the buffer. In the picture you can see that it takes less than a microsecond to do this.

The data is stable for at least 2.5 microseconds so we are pretty safe there.



That's pretty much all there is to it. I did not make a full shield; I just put a header on a piece of 'veroboard' to hold the driver chip and connected the 5V with a wire soldered to a header pin.

Below is the sketch.

/*
 * Program to read PC-STAGE parallel port output and send DMX
 * supports up to 512 channels on UNO board
 * requires DMX library from http://blog.deskontrol.net/arduino-four-universes-dmx-512-library/
 * in lib_dmx.h comment out defines for  UARTs 1,2 and 3
 * 
 * Author: Koen Seru
 * Version: 1 (November 2016)
 * 
 * CONNECTING YOUR ARDUINO BOARD TO DMX CONTROLLED EQUIPMENT IS POTENTIALLY DANGEROUS
 * USE AT YOUR OWN RISK! 
 */

#include <lib_dmx.h >

const int ledPin =  13;      // the number of the LED pin
byte pt = 0; //this byte holds the channel value to be transmitted
boolean boflag = 0;

//pinb 2 is strobe (pin10)
//pinb 3 is start (pin11)
//edit here the number of dimmerchannels to send 
//max is 512, set PCstage to any value but not higher than the value defined here
#define MAXCHANNELS 512


void setup() {
  // put your setup code here, to run once:
  pinMode(ledPin, OUTPUT);

  //set up input pins
  pinMode(2,INPUT_PULLUP);
  pinMode(3,INPUT_PULLUP);
  pinMode(4,INPUT_PULLUP);
  pinMode(5,INPUT_PULLUP);
  pinMode(6,INPUT_PULLUP);
  pinMode(7,INPUT_PULLUP);
  pinMode(8,INPUT_PULLUP);
  pinMode(9,INPUT_PULLUP);
  pinMode(10,INPUT_PULLUP);
  pinMode(11,INPUT_PULLUP);

 ArduinoDmx0.set_tx_address(1);
 ArduinoDmx0.set_tx_channels(MAXCHANNELS);

 ArduinoDmx0.set_control_pin(-1);
 ArduinoDmx0.attachTXInterrupt(rdlpt);
 ArduinoDmx0.init_tx(DMX512);
}

void loop() {
  //there is no main loop code as there is nothing to do here
  //DMX is constantly sent
  //at the end of each packet rdlpt is called
  //when this is finished tx resumes
  //PCstage must be connected for DMX output to happen.


  
}

void rdlpt() {
  //try to fill the complete buffer
  ArduinoDmx0.set_tx_channels(MAXCHANNELS);
  //wait for the start bit to go low
  //digitalRead() is too slow to use here, mask out pin register instead 
  while (PINB &= B001000){}
  boflag = 0; //reset break out 
  //now wait until high to skip the start code (PCstage does not set it)
  //the library takes care of the start code
  while (!(PINB &= B001000)){}
  //loop to fill the buffer
  for (int lamp = 0; lamp < MAXCHANNELS; lamp++){ 

    //wait for clock to go low 
    int boctr = 0;
    while (PINB &= B000100) {
      //check for timeout
      boctr++; 
        if (boctr > 50){
          boflag = 1;
          break;
        }      
      }
    //check for break out
    if (boflag){
      //adjust the buffer size
      ArduinoDmx0.set_tx_channels(lamp);
      //exit loop
      break;
      }      
    //we have approximately 4µS to fill the buffer
    PORTB =B100000; //debug
    //combine D0 and D1 from portB with D2 to D7 from portD
    pt = (B00000011 & PINB) | (B11111100 & PIND);
    ArduinoDmx0.TxBuffer[lamp] = pt;
    PORTB = B000000; //debug
    //wait for the clock to go high again
    while (!(PINB &= B000100)) {}
  }
}

Comments