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.
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.
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:
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:
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!
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.