Couple of quick questions. I have a DMX king USB lighting controller that I'm trying to control.
It's based on the Open DMX protocol (from Entec) who make available a c# class.
I've got the device plugged into an RGB can, and if I test the USB device with their driver, it connects to COM4 and when I switch their software into transmit mode, I can then set individual DMX channels.
Using their OpenDMX class, with a few modifications (the core is the same, i've just added some extra error checking, I can locate the device, query it's information etc.
When I open the device I get a handle.
I can write to that device with FT_Write but no matter what I do, no lights actually come on.
Here's a few relevant code snippets:
public static byte[] buffer;
[DllImport("FTD2XX.dll")]
public static extern FT_STATUS FT_Open(UInt32 uiPort, ref uint ftHandle);
[DllImport("FTD2XX.dll")]
public static extern FT_STATUS FT_Write(uint ftHandle, IntPtr lpBuffer, UInt32 dwBytesToRead, ref UInt32 lpdwBytesWritten);
public static void writeData()
{
while (!done)
{
try
{
initOpenDMX();
status = FT_SetBreakOn(handle);
status = FT_SetBreakOff(handle);
bytesWritten = write(handle, buffer, buffer.Length);
if (bytesWritten == 0)
{
break;
}
System.Threading.Thread.Sleep(25);
}
catch (Exception)
{
break;
}
}
Connected = false;
done = false;
}
All the status come back as FT_Ok, and bytesWritten comes back as 512 (the number of channels on this USB controller)
I keep thinking I've missed something like setting the device into a transmit mode or similar (it only has one DMX socket)
public static void initOpenDMX()
{
status = FT_ResetDevice(handle);
status = FT_SetDivisor(handle, (char)12); // set baud rate
status = FT_SetDataCharacteristics(handle, BITS_8, STOP_BITS_2, PARITY_NONE);
status = FT_SetFlowControl(handle, (char)FLOW_NONE, 0, 0);
status = FT_ClrRts(handle);
status = FT_SetLatencyTimer(handle, (byte)40);
status = FT_Purge(handle, PURGE_TX);
status = FT_Purge(handle, PURGE_RX);
}
I've also tried the Entec OpenDMX class without any modifications from me and it doesn't seem to do anything either.
Just want to stress that their control software is working fine, so the light and controller are compatible. I think something is missing in the way I'm using FTD2xx.
There's no errors coming through (everything is FT_OK) so this suggests the DLL is working - especially since I can query the device using the FT_ListDevices and FT_GetDeviceInfo methods.
Any ideas?
Gareth
To resolve this, I emailed the manufacturer.
It turned out that the device wasn't OpenDMX, in fact it was a DMXProUSB
The protocol was pretty similar, and it was based on the FTDI chip which is why the code partly worked, but it has a microcontroller in it.
I converted the C++ example controller file to C# and got it all working.
If this ever comes up again, I am happy to share the resulting c# code for the DMXProUSB however with no support.
I have emailed the code to the manfacturer (dmxking) and have placed a copy on github: https://github.com/agrath/Sniper.Lighting.Dmx
Thanks for your help
I ran Hippy's VB version through a mechanical VB to C# translator and found one key
difference. FT_WRITE uses a string to pass data to the unmanaged code. The C# class uses
an IPtr pointing to a byte array.
This version works for me:
using System;
using System.Runtime.InteropServices;
using System.Threading;
// based on Hippy's VB Example
// http://members.westnet.com.au/rowanmac/opendmx.html#tx
// Working link: https://web.archive.org/web/20150217155014/http://members.westnet.com.au:80/rowanmac/opendmx.html
namespace Test
{
class Program
{
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_Open(short intDeviceNumber, ref int lngHandle);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_Close(int lngHandle);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_SetDivisor(int lngHandle, int div);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_Read(int lngHandle, string lpszBuffer, int lngBufferSize, ref int lngBytesReturned);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_Write(int lngHandle, string lpszBuffer, int lngBufferSize, ref int lngBytesWritten);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_Write(int lngHandle, IntPtr lpBuffer, int lngBufferSize, ref int lngBytesWritten);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_SetBaudRate(int lngHandle, int lngBaudRate);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_SetDataCharacteristics(int lngHandle, byte byWordLength, byte byStopBits, byte byParity);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_SetFlowControl(int lngHandle, short intFlowControl, byte byXonChar, byte byXoffChar);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_ResetDevice(int lngHandle);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_SetDtr(int lngHandle);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_ClrDtr(int lngHandle);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_SetRts(int lngHandle);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_ClrRts(int lngHandle);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_GetModemStatus(int lngHandle, ref int lngModemStatus);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_Purge(int lngHandle, int lngMask);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_GetStatus(int lngHandle, ref int lngRxBytes, ref int lngTxBytes, ref int lngEventsDWord);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_GetQueueStatus(int lngHandle, ref int lngRxBytes);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_GetEventStatus(int lngHandle, ref int lngEventsDWord);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_SetChars(int lngHandle, byte byEventChar, byte byEventCharEnabled, byte byErrorChar, byte byErrorCharEnabled);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_SetTimeouts(int lngHandle, int lngReadTimeout, int lngWriteTimeout);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_SetBreakOn(int lngHandle);
[DllImport("FTD2XX.DLL", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int FT_SetBreakOff(int lngHandle);
// FTDI Constants
const short FT_OK = 0;
const short FT_INVALID_HANDLE = 1;
const short FT_DEVICE_NOT_FOUND = 2;
const short FT_DEVICE_NOT_OPENED = 3;
const short FT_IO_ERROR = 4;
const short FT_INSUFFICIENT_RESOURCES = 5;
// Word Lengths
const byte FT_BITS_8 = 8;
// Stop Bits
const byte FT_STOP_BITS_2 = 2;
// Parity
const byte FT_PARITY_NONE = 0;
// Flow Control
const byte FT_FLOW_NONE = 0x0;
// Purge rx and tx buffers
const byte FT_PURGE_RX = 1;
const byte FT_PURGE_TX = 2;
public static int handle=0;
public static byte[] buffer = new byte[4]; // can be up to 512, shorter is faster
private static string lpszBuffer=""+ (char) 0 + (char) 64 + (char) 64+ (char) 0;
static void Main(string[] args)
{
init();
}
public static string init()
{
short n = 0;
// ==== ATTEMPT TO OPEN DEVICE ====
if (FT_Open(n, ref handle) != FT_OK)
{
return "FTTD Not Found";
}
// ==== PREPARE DEVICE FOR DMX TRANSMISSION ====
// reset the device
if (FT_ResetDevice(handle) != FT_OK)
{
return "Failed To Reset Device!";
}
// get an ID from the widget from jumpers
// GetID(ref n);
// set the baud rate
if (FT_SetDivisor(handle, 12) != FT_OK)
{
return "Failed To Set Baud Rate!";
}
// shape the line
if (FT_SetDataCharacteristics(handle, FT_BITS_8, FT_STOP_BITS_2, FT_PARITY_NONE) != FT_OK)
{
return "Failed To Set Data Characteristics!";
}
// no flow control
if (FT_SetFlowControl(handle, FT_FLOW_NONE, 0, 0) != FT_OK)
{
return "Failed to set flow control!";
}
// set bus transiever to transmit enable
if (FT_ClrRts(handle) != FT_OK)
{
return "Failed to set RS485 to send!";
}
// Clear TX & RX buffers
if (FT_Purge(handle, FT_PURGE_TX) != FT_OK)
{
return "Failed to purge TX buffer!";
}
// empty buffers
if (FT_Purge(handle, FT_PURGE_RX) != FT_OK)
{
return "Failed to purge RX buffer!";
}
setDmxValue(0, 0); // should always be zero
setDmxValue(1, 64);
setDmxValue(2, 64);
setDmxValue(3, 0);
Thread thread = new Thread(new ThreadStart(writeDataThread));
thread.Start();
return "Ok";
}
// init
public static void setDmxValue(int channel, byte value)
{
buffer[channel] = value;
lpszBuffer="";
for (int i = 0; i < buffer.Length; ++i)
{
lpszBuffer += (char)buffer[i];
}
}
public static void writeDataThread()
{
bool done = false;
int lngBytesWritten=0;
while (!done)
{
FT_SetBreakOn(handle);
FT_SetBreakOff(handle);
FT_Write(handle, lpszBuffer, buffer.Length, ref lngBytesWritten);
System.Threading.Thread.Sleep(50);
}
}
}
}
I wrote the C# class on the open dmx site.
It was based on Hippy's Open DMX driver written in VB.
// Old broken link:
http://members.westnet.com.au/rowanmac/opendmx.html#tx
// Working link: https://web.archive.org/web/20150217155014/http://members.westnet.com.au:80/rowanmac/opendmx.html
The C# Class in not initializing something on the FDDI chip.
This has been tormenting me for years.
I have my suspicions about FT_SetFlowControl. In the VB app, the second parameter is a short int. But any call to it's equivalent in the C# class will only work if the second parameter is cast to a char.
[DllImport("FTD2XX.dll")]
public static extern FT_STATUS FT_SetFlowControl(uint ftHandle, UInt16 usFlowControl, byte uXon, byte uXoff);
I fix the problem with Marshal.Copy
public static void writeDataThread(int Length)
{
int lngBytesWritten = 0;
IntPtr pnt = Marshal.AllocHGlobal(Length);
Marshal.Copy(buffer, 0, pnt, Length);
FT_SetBreakOn(handle);
FT_SetBreakOff(handle);
string StartCode = null;
FT_Write(handle, StartCode, 1, ref lngBytesWritten);
FT_Write(handle, pnt, Length, ref lngBytesWritten);
}
FTDI chips have GPIO pins in addition to the serial ports. Hopefully the documentation tells you whether these need to be set. The function is FT_SetBitMode
. Documentation here.