DMX Steuerung Selbstbau? Teil 3 Mit C DMX Daten Senden

Im letzten Beitrag ging es um das Protokoll zum ansteuern des eurolite Interfaces. Jetzt soll das natürlich auch in ein C Programm gegossen werden. Auch hier hat mich der Linux Treiber fast zum verzweifeln gebracht. Aber der Reihe nach.

Puffer anlegen und Initialisieren

Zuerst muss ein Puffer angelegt werden. Dieser soll später gesendet werden. Eigentlich ist dies in dieser Funktion nicht notwendig. Später wird diese Funktionalität in eine andere Funktion kommen. Aber immerhin können wir so eine Datei zum Testen erzeugen.

  //***************************************************
  // Init Buffer;
  //***************************************************
  for(i=0;i<ANZAHL_BYTES;i++)
  {
    Buffer[i]=0;
  }
  Buffer[0]=0x7E; //Startbyte
  Buffer[1]=0x06; //Protokollkennung
  Buffer[2]=0x01; //Anzahl Bytes niederwertiger Teil
  Buffer[3]=0x02; //Anzahl Bytes höherwertiger Teil
  Buffer[4]=0x00; //???
  Buffer[ANZAHL_BYTES-1]=0xE7; //Endbyte

Es wird der Nachrichten „Header“ erstellt. Das Endbyte wird eingetragen und alle Werte auf 0 gesetzt.

Puffer in eine Datei Schreiben

Wir erfinden Kurzerhand einen eigenen Dateityp für unser Interface und nennen es .dmx. Darin ist die Struktur aus unserem Puffer abgelegt. Und nichts anderes.

fDaten_Datei = fopen("SendeDaten.dmx","w+");
fwrite(Buffer,ANZAHL_BYTES,1,fDaten_Datei);
fclose(fDaten_Datei);

Jetzt gibt es die Datei „SendeDaten.dmx“. Im Hexeditor sieht das folgendermaßen aus:

Daten in Struktur zurücklesen

Jetzt können die Daten in die Struktur zurückgelesen werden. Damit gibt es eine einfache Möglichkeit des Datenaustausches. Ein anderes Programm kann die Datei Modifizieren. Dabei kann es es zu „Kollisionen“ kommen. Die sind aber kein Problem. Wenn der Zugriff nicht geklappt hat, wird es einfach später wieder versucht.

      //***************************************************
      // Hole Daten vom Dateisystem
      //***************************************************
      fDaten_Datei = fopen("SendeDaten.dmx","r");
      if(fDaten_Datei == 0)
      {
         printf("SendeDaten.dmx fehlt! \n");
      }
      else
      {
        fread(Buffer,ANZAHL_BYTES,1,fDaten_Datei);
        //***************************************************
        // Schließe Datei
        //***************************************************
        fclose(fDaten_Datei);
      }

Das Programm sendet einfach immer die letzten Daten die es ausgelesen hat.

Öffnen der Schnittstelle

Unter Linux sieht die Schnittstelle wie eine Datei aus. Das System weist dem Interface einen Namen zu. Ich kenne den Namen bei mir genau, kann ihn deshalb direkt angeben. Das funktioniert aber nicht überall! Unter Umständen muss man sich etwas mehr Gedanken darüber machen.

  //***************************************************
  // Öffne Schnittstellendatei
  //***************************************************
  iPortFD = open("/dev/ttyUSB0",O_RDWR | O_NOCTTY);
  if (iPortFD <= 0)
  {
    printf("Kann Serielle Schnittstelle nicht öffnen\n");
  }

Nach diesem Öffnen hat man exklusiven zugriff auf die Schnittstelle.

Konfigurieren der Schnittstelle

Unter Linux kann man nicht einfach 250000Baud als Geschwindigkeit einstellen. In den „höheren“ Softwareschichten sind nur Standardbaudraten erlaubt. Ich habe ein Variante gefunden die Funktioniert. Das aber aber auch Zufall sein. Mit den zu konfigurierenden Flags habe ich mich nicht tiefgehend auseinandergesetzt. Gefunden ahbe ich die Konfiguration hier: https://gist.github.com/lategoodbye/f2d76134aa6c404cd92c#file-anybaud-c Das die Werte bei mir fest Kodiert sind, ist ein schlechter Stil, aber mein Programm muss immer mit der gleichen Baudrate, der gleichen Länge und dem gleichen Header funktionieren.

  //***************************************************
  // Erstelle Konfigurationsstruktur
  //***************************************************
  ioctl(iPortFD, TCGETS2, config);
  config.c_cflag &= ~CBAUD;
  config.c_cflag |= BOTHER;
  config.c_ispeed = 250000;
  config.c_ospeed = 250000;


  //***************************************************
  // Schreibe Konfiguration
  //***************************************************
  iReturn = ioctl(iPortFD,TCSETS2, &config);
  if(iReturn != 0)
  {
     printf("Fehler ioctl: %x",iReturn);
  }

Diese Funktionalität setzt übrigens folgende Header voraus:

#include "/usr/include/asm-generic/termbits.h"
#include "/usr/include/asm-generic/ioctls.h"

Senden der Daten

Um die Daten senden zu können, muss die Schnittstelle erfolgreich geöffnet und korrekt Konfiguriert sein. Ebenfalls sollten gültige Daten verfügbar sein. Dieses Programm verfügt aber über keinerlei Kontrolle, ob die Daten wirklich gültig sind.

  //***************************************************
  // Sende Daten
  //***************************************************
    intSendBytes = write (iPortFD, Buffer, ANZAHL_BYTES);
    if(intSendBytes != ANZAHL_BYTES)
    {
      printf("Daten konnten nicht geschrieben werden. Rückgabe: %x \n",intSendBytes);
    }

    sleep(1);

Zum Schluss kann noch überprüft werden, ob die Daten wirklich übertragen wurden. Es reicht für das Interface ein mal pro Sekunde zu senden. Wenn die Daten korrekt sind, leuchtet die LED grün.

Übersetzen

Unter Linux ist das Übersetzen ziemlich einfach (Wenn die richtigen Pakete installiert sind). Man benötigt eine Datei mit dem Quelltext. Zum Beispiel dmx_senden.c. Mit dem Befehl „gcc dmx_senden.c -o dmx_senden “ wird sie übersetzt. Und mit dem Befehl „./dmx_senden“ gestartet.

Außerdem ist es einfacher, eine Mainfunktion für den Einsprung zu erzeugen. Deshalb gesellt sich zu unsere Schreibfunktion, noch eine Zweite.

Programm Ausführen

Kanal 1 hat Wert 255
Kanal 3 hat wert 255

Mit einem Hex-Editor Werte einzustellen ist nicht toll, aber als Zwischenschritt schön, weil man sieht das etwas passiert.

Der komplette Quelltext

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#include "/usr/include/asm-generic/termbits.h"
#include "/usr/include/asm-generic/ioctls.h"

#define ANZAHL_BYTES 518
#define DATEN_OFFSET 5

void vSchreibProzess (void);



int main()
{
  int i;
  int iReturn;

  //************************************************************************/
  //* 
  //************************************************************************/


  //************************************************************************/
  //* 
  //************************************************************************/
int pid; 
pid=fork();

  if(pid == -1)
  {
    //Fehler. Fork nicht möglich
    printf("Schreibprozess konnte nicht angelegt werden \n");
  }
  else if(pid == 0)
  {
    //Lese Daten Prozess
    printf("Erzeuge Schreibprozess\n");
    vSchreibProzess();
    exit(0);
  }

for(;;)
  {
    printf("Mainprozess\n");
    sleep(10);
  }
}


//***************************************************
//
//***************************************************


//************************************************************************/
//* Schreibprozess
//************************************************************************/
void vSchreibProzess(void){
  //***************************************************
  // Variablen
  //***************************************************
  int intSendBytes,intReadBytes; //Rückgabeparameter von Funktionen
  int iPortFD;                   //Datei Handle für Serielle Schnittstelle
  struct termios2 config;        //Konfiguration für Serielle Schnittstelle
  int iReturn;                   //Rückgabewert Konfiguration
  char Buffer[ANZAHL_BYTES];     //Puffer für Daten
  int i;                         //Zählvariable für Schleife
  FILE *fDaten_Datei;            //Datei Handle für DMX Werte

  //***************************************************
  // Code
  //***************************************************
  //***************************************************
  // Öffne Schnittstellendatei
  //***************************************************
  iPortFD = open("/dev/ttyUSB0",O_RDWR | O_NOCTTY);
  if (iPortFD <= 0)
  {
    printf("Kann Serielle Schnittstelle nicht öffnen\n");
  }

  //***************************************************
  // Erstelle Konfigurationsstruktur
  //***************************************************
  ioctl(iPortFD, TCGETS2, config);
  config.c_cflag &= ~CBAUD;
  config.c_cflag |= BOTHER;
  config.c_ispeed = 250000;
  config.c_ospeed = 250000;


  //***************************************************
  // Schreibe Konfiguration
  //***************************************************
  iReturn = ioctl(iPortFD,TCSETS2, &config);
  if(iReturn != 0)
  {
     printf("Fehler ioctl: %x",iReturn);
  }
 
  //***************************************************
  // Schleife für Ausführung
  //***************************************************
  for(;;)
  {

      //***************************************************
      // Hole Daten vom Dateisystem
      //***************************************************
      fDaten_Datei = fopen("SendeDaten.dmx","r");
      if(fDaten_Datei == 0)
      {
         printf("SendeDaten.dmx fehlt! \n");
      }
      else
      {
        fread(Buffer,ANZAHL_BYTES,1,fDaten_Datei);
        //***************************************************
        // Schließe Datei
        //***************************************************
        fclose(fDaten_Datei);
      }
  //***************************************************
  // Sende Daten
  //***************************************************
    intSendBytes = write (iPortFD, Buffer, ANZAHL_BYTES);
    if(intSendBytes != ANZAHL_BYTES)
    {
      printf("Daten konnten nicht geschrieben werden. Rückgabe: %x \n",intSendBytes);
    }

    sleep(1);
  }
return;
}