Beitragsbild GPIO für Linux

Arduino WiringPI GPIO für Linux

Jeder der den gpio Befehl von WiringPi am Raspberry Pi kennt, schätzt die einfache Steuerung von GPIO Pins ohne großen Programmieraufwand. Mit dem Raspberry Pi habe ich so schon viele Sensoren, Servos, Relays und elektronische Schaltungen gesteuert, oft für meine RC Autos.

Linux Home PC mit WiringPI GPIO auf RC Auto Chassis
Linux Home PC mit GPIO auf RC Auto Chassis

Bei einem Linux-PC oder Home-Server stehen leider keine freien I/O Ports zur Verfügung und da ich die Freiheit der Elektronik bei meinem HTPC vermisse, habe ich beschlossen eine Arduino über den USB Port von Linux aus anzusteuern und mir die gewünschten GPIO Ports per USB nachzurüsten.

In meinem heutigen Blog präsentiere ich einen selbst programmierten gpio Befehl für Linux mit derselben Befehlssytax des Raspberry Pis und eine einfache MOSFET Steuerung mit einem IRFZ44N, um einen DC Motor ein- und auszuschalten.

Linux Screenshot WiringPi gpio Kommando
Linux Screenshot gpio Kommando

gpio Befehlssyntax und Sourcecode

Linux gpio Befehlssyntax

Die Befehlssyntax meines gpio Befehls ist der von WiringPi angepasst:

Befehl zum Schalten des Pins 7 auf "HIGH" (5V)
$ ./gpio write 7 1
Beispielbefehl zum Schalten des Pins 7 auf "LOW" (0V)
$ ./gpio write 7 0
Beispielbefehl zum Schalten des Pins 3 auf 2,5V,
Wertebereich: 0 (0V) bis 255 (5,0V bzw. Vin)
./gpio awrite 3 127
Auslesen eines digitalen GPIO Eingang Pins 7,
Wertebereich: 0 "LOW" oder 1 "HIGH"
$ ./gpio read 7
Read: 0
Auslesen eines analogen GPIO Eingang Pins 5.
Wertebereich: 0 (0V) bis 1023 (5,0V bzw. Vin)
$ ./gpio aread 5
Read: 424
Linux Befehl WirINGPI gpio: Download C++ Sourcecode
/* 
   Linux gpio command with serial USB protocol on /dev/ttyUSB0
   Compile: g++ -std=c++11 gpio.cc -o gpio
   V1.01 (c) 2016 Andreas Schuster
*/
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string>
#include <cstring>
#include <sstream>
#include <iostream>
#include <cstdlib>
#include <cerrno>


#define BAUDRATE B38400
#define DEVICE "/dev/ttyUSB0"
#define FALSE 0
#define TRUE 1

using namespace std;

int debug = 0;

int USB;
int n_written;
int action = 0;
std::string status;
int pin = -1;

int main( int argc, char *argv[] )
{
    struct stat file_stats;
    time_t rawtime;
    time (&rawtime );
    int param = 1;

    if(argc < 2) {
        std::cerr << "Usage:\n" << argv[0] << " write pin# {0|1}\n";
        std::cerr << " or" << argv[0] << " awrite pin# 0..255\n";
        std::cerr << " or " << argv[0] << " tone{%[frequency]}\n";
        std::cerr << " or " << argv[0] << " read pin#\n";
        std::cerr << " or " << argv[0] << " aread pin#\n";
        return 100;
    }

        std::string command  = argv[param];
        if ( command == "write" || command == "read" || command == "awrite" || command == "aread" ) {
        param++;
                std::string pinparam = argv[param];
        pin = atoi(pinparam.c_str());
            action = 1;
        }
    if ( command.substr(0,4) == "tone") {
        action = 2;
    }
    param++;
    if ( argc > param ) {
        std::string mandatory = argv[param];
        if ( mandatory == "1" ) {
            status = "HIGH";
            if (debug) std::cout << "mandatory HIGH set!\n";
        } else if ( mandatory  == "0" ) {
            status = "LOW";
            if (debug) std::cout << "mandatory LOW set!\n";
        } else {
            status = mandatory;
        }
    }

    if (action > 0) {
        /* Open File Descriptor */
        USB = open( DEVICE, O_RDWR| O_NOCTTY );
        if (!USB) {
            std::cerr << "Error " << errno << " from open: " << std::strerror(errno) << std::endl;
            return 1;
        }

        struct termios tty;
        struct termios tty_old;
        memset (&tty, 0, sizeof tty);

        /* Error Handling */
        if ( tcgetattr ( USB, &tty ) != 0 ) {
            std::cerr << "Error " << errno << " from tcgetattr: " << strerror(errno) << std::endl;
        }

        /* Save old tty parameters */
        tty_old = tty;

        /* Set Baud Rate */
        if(cfsetospeed (&tty, (speed_t)BAUDRATE) != 0)
            std::cout << "Error " << errno << " from cfsetospeed: " << strerror(errno) << std::endl;
        if(cfsetispeed (&tty, (speed_t)BAUDRATE) != 0)
            std::cout << "Error " << errno << " from cfsetispeed: " << strerror(errno) << std::endl;

        /* Setting other Port Stuff */ 
        tty.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;
        tty.c_cflag &= ~HUPCL;

        tty.c_cc[VINTR]    = 0;     /* Ctrl-c */
        tty.c_cc[VQUIT]    = 0;     /* Ctrl-\ */
        tty.c_cc[VERASE]   = 0;     /* del */
        tty.c_cc[VKILL]    = 0;     /* @ */
        tty.c_cc[VEOF]     = 4;     /* Ctrl-d */
        tty.c_cc[VTIME]    = 35;     /* inter-character timer unused */
        tty.c_cc[VMIN]     = 0;     /* blocking read until 1 character arrives */
        tty.c_cc[VSWTC]    = 0;     /* '\0' */
        tty.c_cc[VSTART]   = 0;     /* Ctrl-q */
        tty.c_cc[VSTOP]    = 0;     /* Ctrl-s */
        tty.c_cc[VSUSP]    = 0;     /* Ctrl-z */
        tty.c_cc[VEOL]     = 0;     /* '\0' */
        tty.c_cc[VREPRINT] = 0;     /* Ctrl-r */
        tty.c_cc[VDISCARD] = 0;     /* Ctrl-u */
        tty.c_cc[VWERASE]  = 0;     /* Ctrl-w */
        tty.c_cc[VLNEXT]   = 0;     /* Ctrl-v */
        tty.c_cc[VEOL2]    = 0;     /* '\0' */

        tty.c_iflag = IGNPAR | ICRNL;

        tty.c_oflag = 0;  // raw output

        //tty.c_lflag = ICANON;
        tty.c_lflag = ~ICANON;

        /* Flush Port, then applies attributes */
        if (tcflush( USB, TCIFLUSH ) != 0) 
            std::cerr << "Error " << errno << " from tcflush: " << strerror(errno) << std::endl;
        if ( tcsetattr ( USB, TCSANOW, &tty ) != 0) {
            std::cerr << "Error " << errno << " from tcsetattr" << std::endl;
        }

        /* Write */
        usleep(1700000); // wait 0,7 sec if Arduino need a reset on USB connect

        std::string cmd = "";
        if ( command.substr(0,5) == "write") {
            cmd = "^out_" + std::to_string(pin) + "%" + status + "$\n";
        } else if (command.substr(0,6) == "awrite") {
            cmd = "^aout_" + std::to_string(pin) + "%" + status + "$\n";
        } else if (command.substr(0,4) == "read") {
            cmd = "^in_" + std::to_string(pin) + "%" + status + "$\n";
        } else if (command.substr(0,5) == "aread") {
            cmd = "^ain_" + std::to_string(pin) + "%" + status + "$\n";
        } else if (command.substr(0,4) == "tone" && status == "HIGH") {
            //cmd = "^tone%3000$\n";
            cmd = "^"+command+"$\n";
        }

                //std::cerr << "executing on Arduino: " << cmd;
        n_written = 0,

        n_written = write( USB, cmd.c_str(), cmd.size() );
        if (!n_written) 
            std::cerr << "Error " << errno << " from write: " << strerror(errno) << std::endl;

        /* Read result */
        int n = 0;
        char buf[255];
        memset(buf, '\0', sizeof buf);

        n = read( USB, buf, 255 );
        buf[n]=0;

        if (n < 0) {
            std::cerr << "Error reading: " << strerror(errno) << std::endl;
        }
        else if (n == 0) {
            std::cerr << "Read nothing!" << std::endl;
        }
        else {
            std::cerr << "Read: " << buf << std::endl;
        }

        close(USB);
    } else {               // else action < 1
        std::cerr << "Error: no valid command given!" << std::endl;
        return 2;
    }
}

Die Arduino Schaltung

Die WiringPi gpio Arduino Schnittstelle

Den Arduino habe ich mit einem einfachen Sketch bestückt, der serielle Befehle am USB Port empfängt und in die PortWrite und PortRead Befehle des Arduino übersetzt. Die genaue Syntax des Protokolls zwischen dem Linux gpio Kommando und dem Arduino entnimm bitte dem Source-Code.

Den WiringPi gpio ähnlichen Source-Code für Arduino habe ich selbst entwickelt, in meinem Fall nutze ich einen kleinen Arduino Nano 3.0.

Arduino Nano/Uno/Mega/Due Sketch: Download Arduino Source-Code
/*
   My own Linux to Arduino project with simple
   communication protocol on /dev/ttyUSB0
   V1.02 (c) 2016 Andreas Schuster
*/
const boolean debug = false;

String inputString = "";         // a string to hold incoming data
String cmdString = "";
String valueString = "";
boolean stringComplete = false;  // whether the string is complete
boolean cmdComplete = false;
boolean valueComplete = false;

const int ledPin = 13;

int ledstatus = 0;
int startcmd = 0;
int valuecmd = 0;
int endcmd = 0;
int serialcounter = 0;
int timer = 0;
int pin = 0;
int val = 0;
int freq = 0;
int valueInt = 0;

void setup() {
  // initialize serial:
  Serial.begin(38400);
  // reserve 200 bytes for the inputString:
  inputString.reserve(200);
  cmdString.reserve(40);
  valueString.reserve(40);
  pinMode(ledPin, OUTPUT);
  if (debug) {
      Serial.print("Welcome to Linux GPIO");
      Serial.print("\n");
  }
}

void loop() {
  // change the onboard LED when a newline arrives:
  if (stringComplete) {
    if (debug) {
      Serial.print(inputString);
      Serial.print("\n");
    }
    timer = 0;
    if (ledstatus == 0) {
      digitalWrite(ledPin, HIGH);
      ledstatus = 1;
    } else {
      digitalWrite(ledPin, LOW);
      ledstatus = 0;
    }
    // handle cmdString & valueString
    if (cmdComplete && debug) {
       Serial.print("cmd=");
       Serial.print(cmdString);
       Serial.print(";");
    }
    if (valueComplete && debug) {
       Serial.print("value=");
       Serial.print(valueString);
       Serial.print(";");
    }
    pin = 255; // false pin at start

    if (cmdString.length() >= 6) {                // typical command "aout_4" or "aout_12"
      if (cmdString.substring(0, 5) == "aout_") { // found an analog output command
          if (isAlphaNumeric(cmdString.charAt(5))) {
            pin = cmdString.charAt(5) - char('0');
            if (cmdString.length() >= 7) {
              if (isAlphaNumeric(cmdString.charAt(6))) {
                pin = (pin * 10) + (cmdString.charAt(6) - char('0'));
              }
            }
            // write to pin
            if (pin < 100) {
              if (valueString == "HIGH") {
                valueInt = 255;
              } else if (valueString == "LOW") {
                valueInt = 0;
              } else {
                valueInt = valueString.toInt();
                if (valueInt <0 ) {
                  valueInt = 0;
                }
                if (valueInt > 255 ) {
                  valueInt = 255;
                }
              }
              analogWrite(pin,valueInt);
              Serial.print("OK:");
              Serial.print(cmdString);
              Serial.print(" set to ");
              Serial.print(valueInt);
              Serial.print("\n");
            } else {
                Serial.print("ERROR:cannot detect pin number!\n");
            }
          }
      }
    }
    
    if (cmdString.length() >= 5) {                // typical command "out_4" or "out_12"
      if (cmdString.substring(0, 4) == "out_") { // found an output command
          if (debug) {
            Serial.print("found an out_ command\n");
          }
          if (isAlphaNumeric(cmdString.charAt(4))) {
            pin = cmdString.charAt(4) - char('0');
            if (cmdString.length() >= 6) {
              if (isAlphaNumeric(cmdString.charAt(5))) {
                pin = (pin * 10) + (cmdString.charAt(5)- char('0'));
              }
            }
            if (debug) {
              Serial.print(pin);
              Serial.print("=pin\n");
            }
            // write to pin
            if (pin < 100) {
              if (valueString == "HIGH") {
                pinMode(pin, OUTPUT);
                digitalWrite(pin, HIGH);
              } else if (valueString == "LOW") {
                pinMode(pin, OUTPUT);
                digitalWrite(pin, LOW);
              } else {
                Serial.print("ERROR:cannot understand value!\n");
              }
                Serial.print("OK:");
                Serial.print(cmdString);
                Serial.print(" set to ");
                Serial.print(valueString);
                Serial.print("\n");
            } else {
                Serial.print("ERROR:cannot detect pin number!\n");
            }
          }
      }
    }

    if (cmdString.length() >= 5) {              // typical command "ain_4" or "ain_12"
      if (cmdString.substring(0, 4) == "ain_") { // found an input command
          if (isAlphaNumeric(cmdString.charAt(4))) {
            pin = cmdString.charAt(4) - char('0');
            if (cmdString.length() >= 6) {
              if (isAlphaNumeric(cmdString.charAt(5))) {
                pin = (pin * 10) + (cmdString.charAt(5) - char('0'));
              }
            }
            // readpin here
            if (pin < 100) {
              //pinMode(pin, INPUT);
              val = analogRead(pin);
              Serial.print(val);
              Serial.print("\n");
            } else {
               Serial.print("ERROR:cannot detect pin number!\n");
            }
          }
      }
    }
    
    if (cmdString.length() >= 4) {              // typical command "in_4" or "in_12"
      if (cmdString.substring(0, 3) == "in_") { // found an input command
          if (isAlphaNumeric(cmdString.charAt(3))) {
            pin = cmdString.charAt(3) - char('0');
            if (cmdString.length() >= 5) {
              if (isAlphaNumeric(cmdString.charAt(4))) {
                pin = (pin * 10) + (cmdString.charAt(4) - char('0'));
              }
            }
            // readpin here
            if (pin < 100) {
              pinMode(pin, INPUT);
              val = digitalRead(pin);
              if (val == HIGH) {
                Serial.print("1\n");
              } else if (val == LOW) {
                Serial.print("0\n");
              } else {
                Serial.print("?\n");
              }
            } else {
               Serial.print("ERROR:cannot detect pin number!\n");
            }
          }
      }
    }
    
    if (cmdString == "tone") {
      freq = valueString.toInt();
      if (freq < 40) {
        freq = 2600;
      }
      Serial.print("OK:play tone ");
      Serial.print(freq,DEC);
      Serial.print("\n");
      tone(3, freq, 2000);
    }
    if (inputString == "help\n") {
      Serial.print("digital output command:\n");
      Serial.print(" ^out_[pin]%[HIGH|LOW]$\n");
      Serial.print(" return: none\n");
      Serial.print("analog output command:\n");
      Serial.print(" ^aout_[pin]%[HIGH|LOW|0 to 255]$\n");
      Serial.print(" return: none\n");
      Serial.print("digital input commands:\n");
      Serial.print(" ^in_[pin]$\n");
      Serial.print(" return: HIGH | LOW\n");
      Serial.print("analog input commands:\n");
      Serial.print(" ^ain_[pin]$\n");
      Serial.print(" return: value 0 to 1023\n");
      Serial.print("tone command (only pin 3!):\n");
      Serial.print(" ^tone$ or ^tone%[frequency]$\n");
      Serial.print(" return: none\n");
    }
    serialcounter = 0;
    startcmd = 0;
    valuecmd = 0;
    endcmd = 0;
    // clear the string:
    inputString = "";
    cmdString = "";
    valueString = "";
    stringComplete = false;
    cmdComplete = false;
    valueComplete = false;
  }
  delay(2);
  timer++;
  if (timer > 5000) {  //periodically change the onboard LED
    timer = 0;
    if (ledstatus == 0) {
      digitalWrite(ledPin, HIGH);
      ledstatus = 1;
    } else {
      digitalWrite(ledPin, LOW);
      ledstatus = 0;
    }
  }
}

void serialEvent() {
  /* seperate a typical serial command
      e.g. "^out_d4%HIGH$"
      into
      cmdString = "out_d4"
      value = "HIGH"
  */
  while (Serial.available()) {
    // get the new byte:
    serialcounter++;
    char inChar = (char)Serial.read();
    // add it to the inputString:
    //Serial.print(inChar);
    inputString += inChar;
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (startcmd > 0 && inChar != '%' && inChar != '$') {
      cmdString += inChar;
    }
    if (valuecmd > 0 && inChar != '$') {
      valueString += inChar;
    }
    if (inChar == '^') {
      startcmd = serialcounter;
      valuecmd = 0;
    }
    if (inChar == '%') {
      cmdComplete = true;
      valuecmd = serialcounter;
      startcmd = 0;
    }
    if (inChar == '$') {
      valuecmd = 0;
      startcmd = 0;
      cmdComplete = true;
      valueComplete = true;
      endcmd = serialcounter;
    }
    if (inChar == '\n') {
      stringComplete = true;
    }
  }
}

Auf einer Prototypen-Steckplatine habe ich mit einem MOSFET eine 9V Schaltung mit einem Gleichstrom DC Motor aufgebaut:

Sketch DC Motor Steuerung für Arduino über GPIO

Anklicken zum Vergrößern
– Sketch DC Motor Steuerung für Arduino

Der Arduino wird nach der Programmierung per USB-Kabel mit Linux verbunden. Unter Ubuntu Linux erscheint nach dem Anschluss nach wenigen Sekunden das Device /dev/ttyUSB0

Steuern, schalten, regeln mit Linux

Schaltung von Elektronik unter Linux

Sobald der gpio Befehl kompiliert ist und der Arduino per USB Port verbunden ist, steht der Schaltung beliebiger Steuerungen, LEDs oder komplexer elektronischer Aufgaben nichts mehr im Wege.

Hier meine Beispielschaltung eines Gleichstrommotors:

MOSFET DC Motor Steuerung für Arduino per GPIO

Anklicken zum Vergrößern
– MOSFET DC Motor Steuerung für Arduino

Bei meinem Arduino Nano habe ich eine kleine Hürde mit dem bekannten “Reset on USB connect”. Bei jeder neuen USB Verbindung, in meinem Fall bei jeder gpio Befehlsnutzung vollzieht der Arduino einen Reset. Abhilfe hierzu ist unter Linux den Port /dev/ttyUSB0 permanent offen zu halten (Beispiel in meinem anderen Arduino/Linux Blog Beitrag) oder die Nutzung eines Kondensators oder ein Widerstand der am Arduino aufgelötet wird.

Viel Spaß beim Nachbauen meines DIY Projekts!

Ich freue mich sehr auf deinen Kommentare und auch über Verbesserungsvorschlag zur Schaltung, dem Arduino oder Linux C++ Programm.

Ein Gedanke zu „Arduino WiringPI GPIO für Linux“

  1. Hallo.

    Zuerst mal Danke danke danke… genau das was ich gesucht hab.
    Vielleicht zwei Anmerkungen die anderen helfen:

    std::cerr << "Read: " << buf << std::endl;

    Das sollte meiner Meinung nach "std::cout << "Read: " << buf << std::endl;" sein, sonst kann man die ausgabe nicht weiterverarbeiten.

    Ich selbst hab noch "usleep(1700000);" auf "usleep(100000);" gesetzt damit das ganze auch schnell reagiert.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert