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
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
Post a Comment