/* Setzen eines Analogwertes ueber den Baustein LTC 1453
 * Pinbelegung am Raspberry Pi siehe unten
 * Eingabe ist ein Prozentwert (int) zwischen 0 und 100
 * Aufrufbeispiel: ./setdac 45
 *
 * Compilieren mit:
 * gcc -Wall -o setdac setdac.c
 *
 * Die Meldung "'GPIO_Read' defined but not used" kann
 * ignoriert werden (die Funktion ist halt fuer alle
 * Faelle im Quellcode enthalten)
 *
 * Damit unprivilegierte User (pi, www-data usw.) das
 * Programm aufrufen koennen, muss es SUID root gesetzt
 * werden:
 * sudo chown root.root setdac
 * sudo chmod 4711 setdac
 *
 * 11.01.2015  JPL
 */



#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>

/* Symbolische Namen fuer die Datenrichtung und die Daten  */
#define IN  0
#define OUT 1

#define LOW  0
#define HIGH 1

/* Datenpuffer fuer die GPIO-Funktionen */
#define MAXBUFFER 100

/* LTC 1453 Pins */
#define   CLK   22   /* Clock Input */
#define   DIN   23   /* Data Input  */
#define   CS    24   /* Enable      */

/* Funktionsprototypen */
static int GPIO_Export(int pin);
static int GPIO_Unexport(int pin);
static int GPIO_Direction(int pin, int dir);
static int GPIO_Read(int pin);
static int GPIO_Write(int pin, int value);

int delay(unsigned long millis);
void SetData(unsigned int value);

int main(int argc, char **argv)
  {
  unsigned int value;
  int err;

  /************* Beginn Initialisierung *************/

  /* bin ich root? */
  if (geteuid() != 0)
    {
    fprintf(stderr, "You must be root!\n");
    return 1;
    }

  /* Init GPIO-Pins */
  err = GPIO_Export(CLK);
  err += GPIO_Direction(CLK, OUT);
  err += GPIO_Export(DIN);
  err += GPIO_Direction(DIN, OUT);
  err += GPIO_Export(CS);
  err += GPIO_Direction(CS, OUT);

  err += GPIO_Write(CLK, 0);
  err += GPIO_Write(CS, 0);
  err += GPIO_Write(DIN, 0);
  if (err > 0)
    {
    fprintf(stderr, "Can not init GPIO!\n");
    return 2;
    }
  /************** Ende Initialisierung **************/


  if (argc == 2)
    {
    /* String lesen, nach int umwandeln */
    value = atoi(argv[1]);
    /* Wertebereich 0 .. 100 */
    if ((value >= 0) || (value <= 100))
      {
      /* Prozent nach 12-Bit-int wandeln */
      value = (unsigned int)(40.95 * value);
      SetData(value);
      }
    else
      {
      fprintf(stderr, "Argument must be between 0 and 100!\n");
      return 3;
      }
    delay(200);
    }
  GPIO_Unexport(CLK);
  GPIO_Unexport(CS);
  GPIO_Unexport(DIN);
  return 0;
  }


/*
 * 12 Bit Daten an den LTC 1453 senden
 *
 * The data on the DIN input is loaded into the shift register
 * on the rising edge of the clock. The MSB is loaded first. The
 * DAC register loads the data from the shift register when
 * CS/LD is pulled high. The CLK is disabled internally when
 * CS/LD is high. Note: CLK must be low before CS/LD is
 * pulled low to avoid an extra internal clock pulse.
 */
void SetData(unsigned int value)
  {
  int i;
  /* CS muss LOW sein */
  GPIO_Write(CS, LOW);
  /* shift MSB ... LSB (MSB zuerst) */
  for(i = 11; i >= 0; i--)
    {
    /* set data bit, check MSB  */
    if (value & 0x800)
      GPIO_Write(DIN, HIGH);
    else
      GPIO_Write(DIN, LOW);
    value = value << 1;
    /* CLK-Impuls senden und bei
       Bit 0 CS/LD-Impuls auf HIGH.
       (siehe Datenblatt) */
    GPIO_Write(CLK, HIGH);
    if (i == 0) GPIO_Write(CS, HIGH);
    GPIO_Write(CLK, LOW);
    }
  GPIO_Write(CS, LOW);
  }


/*
 * Delay (warten) - Zeitangabe in Millisekunden
 */
int delay(unsigned long millis)
  {
  struct timespec ts;
  int err;

  ts.tv_sec = millis / 1000;
  ts.tv_nsec = (millis % 1000) * 1000000L;
  err = nanosleep(&ts, (struct timespec *)NULL);
  return (err);
  }


/* GPIO-Pin aktivieren
 * Schreiben Pinnummer nach /sys/class/gpio/export
 * Ergebnis: 0 = O.K., -1 = Fehler
 */
static int GPIO_Export(int pin)
  {
  char buffer[MAXBUFFER];    /* Output Buffer   */
  ssize_t bytes;             /* Datensatzlaenge */
  int fd;                    /* Filedescriptor  */
  int res;                   /* Ergebnis von write */

  fd = open("/sys/class/gpio/export", O_WRONLY);
  if (fd < 0)
    {
    perror("Kann nicht auf export schreiben!\n");
    return(-1);
    }
  bytes = snprintf(buffer, MAXBUFFER, "%d", pin);
  res = write(fd, buffer, bytes);
  if (res < 0)
    {
    perror("Kann Pin nicht aktivieren (write)!\n");
    return(-1);
    }
   close(fd);
  return(0);
  }

/* GPIO-Pin deaktivieren
 * Schreiben Pinnummer nach /sys/class/gpio/unexport
 * Ergebnis: 0 = O.K., -1 = Fehler
 */
static int GPIO_Unexport(int pin)
  {
  char buffer[MAXBUFFER];    /* Output Buffer   */
  ssize_t bytes;             /* Datensatzlaenge */
  int fd;                    /* Filedescriptor  */
  int res;                   /* Ergebnis von write */

  fd = open("/sys/class/gpio/unexport", O_WRONLY);
  if (fd < 0)
    {
    perror("Kann nicht auf unexport schreiben!\n");
    return(-1);
    }
  bytes = snprintf(buffer, MAXBUFFER, "%d", pin);
  res = write(fd, buffer, bytes);
  if (res < 0)
    {
    perror("Kann Pin nicht deaktivieren (write)!\n");
    return(-1);
    }
  close(fd);
  return(0);
  }

/* Datenrichtung GPIO-Pin festlegen
 * Schreiben Pinnummer nach /sys/class/gpioXX/direction
 * Ergebnis: 0 = O.K., -1 = Fehler
 */
static int GPIO_Direction(int pin, int dir)
  {
  char path[MAXBUFFER];      /* Buffer fuer Pfad   */
  int fd;                    /* Filedescriptor  */
  int res;                   /* Ergebnis von write */

  snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/direction", pin);
  fd = open(path, O_WRONLY);
  if (fd < 0)
    {
    perror("Kann Datenrichtung nicht setzen (open)!\n");
    return(-1);
    }
  switch (dir)
    {
    case IN : res = write(fd,"in",2); break;
    case OUT: res = write(fd,"out",3); break;
    }
  if (res < 0)
    {
    perror("Kann Datenrichtung nicht setzen (write)!\n");
    return(-1);
    }
  close(fd);
  return(0);
  }

/* vom GPIO lesen
 * Ergebnis: -1 = Fehler, 0/1 = Portstatus
 */
static int GPIO_Read(int pin)
  {
  char path[MAXBUFFER];         /* Buffer fuer Pfad     */
  int fd;                       /* Filedescriptor       */
  char result[MAXBUFFER] = {0}; /* Buffer fuer Ergebnis */

  snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/value", pin);
  fd = open(path, O_RDONLY);
  if (fd < 0)
    {
    perror("Kann vom GPIO nicht lesen (open)!\n");
    return(-1);
    }
  if (read(fd, result, 3) < 0)
    {
    perror("Kann vom GPIO nicht lesen (read)!\n");
    return(-1);
    }
  close(fd);
  return(atoi(result));
  }

/* auf GPIO schreiben
 * Ergebnis: -1 = Fehler, 0 = O.K.
 */
static int GPIO_Write(int pin, int value)
  {
  char path[MAXBUFFER];      /* Buffer fuer Pfad   */
  int fd;                    /* Filedescriptor     */
  int res;                   /* Ergebnis von write */

  snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/value", pin);
  fd = open(path, O_WRONLY);
  if (fd < 0)
    {
    perror("Kann auf GPIO nicht schreiben (open)!\n");
    return(-1);
    }
  switch (value)
    {
    case LOW : res = write(fd,"0",1); break;
    case HIGH: res = write(fd,"1",1); break;
    }
  if (res < 0)
    {
    perror("Kann auf GPIO nicht schreiben (write)!\n");
    return(-1);
    }
  close(fd);
  return(0);
  }
