Schnäppchen

Kategorien

Bidirektionale serielle Kommunikation zwischen Arduino und PC

Für ein Projekt (nämlich unser Zimmergewächshaus) brauchte ich eine vernünftige Möglichkeit aus einem C++ Programm auf einem Linux-PC bidirektional mit dem Arduino zu kommunizieren. Eigentlich ist das natürlich kein unlösbares Problem. Trotzdem hat es etwas gedauert, bis das bei mir reibungslos funktionierte.

Die hier vorgestellte Test-Umgebung macht folgendes: Die Zeichen, die auf dem PC eintipperten wurden werden zum Arduino übertragen. Dort werden sie zeilenweise gesammelt und dann zurück zum PC übertragen.

Dieses ist ein Test
Arduino: Dieses ist ein Test

Um das Ganze etwas besser testen zu können, werden die Ziffern 1 bis 5 auf dem PC vor der Übertragung in Worte (“eins” bis “fünf”) gewandelt. Die Ziffern 6 bis 0 werden ganz normal übertragen, dann aber auf dem Arduino in die entsprechenden Worte gewandelt.

Ziffern 0 1 2 3 4 5 6 7 8 9
Arduino: Ziffern null eins zwei drei vier fünf sechs sieben acht neun

Hier zunächst der Arduino-Sketch:

// ----------------------------------------------
//  Project Arduino/PC Serial Communication  
// ----------------------------------------------
// Just some code to test serial communication
// between Arduino and PC
//
// Version
//   minicom 2014.02.06
// 
// Author:
//   Heiner Otterstedt <h.otterstedt@gmail.com>
//
// Website:
//   http://maheo.eu
//

char szInstruction[128];
int  iInstruction = 0;

void setup()
{ Serial.begin(9600);
}

void loop()
{ if(Serial.available())
  { while(Serial.available())
    { szInstruction[iInstruction] = Serial.read();
      if(szInstruction[iInstruction] == '6')
      { strcpy(&szInstruction[iInstruction], "sechs");
        iInstruction += 4;
      }
      else if(szInstruction[iInstruction] == '7')
      { strcpy(&szInstruction[iInstruction], "sieben");
        iInstruction += 5;
      }
      else if(szInstruction[iInstruction] == '8')
      { strcpy(&szInstruction[iInstruction], "acht");
        iInstruction += 3;
      }
      else if(szInstruction[iInstruction] == '9')
      { strcpy(&szInstruction[iInstruction], "neun");
        iInstruction += 3;
      }
      else if(szInstruction[iInstruction] == '0')
      { strcpy(&szInstruction[iInstruction], "null");
        iInstruction += 3;
      }

      if(szInstruction[iInstruction] == '\n' ||
         szInstruction[iInstruction] == 'q' ||
         ++iInstruction >= 120)
      { szInstruction[iInstruction] = 0;
        iInstruction = 0;
        doSomething(szInstruction);
      }
    }
  }
}

void doSomething(char *psz)
{ Serial.print  ("Arduino: ");
  Serial.println(szInstruction);
}

Das C++ Programm auf dem Linux-PC habe ich im Prinzip aus einem Beitrag in der Boost Mailingliste (siehe hier bei Nabble) abgeleitet. Der User Jeff Gray-3 hatte dort den Code für ein einfaches MiniCom Programm gepostet. Erwartungsgemäß verwendet das Programm Teile der Boost Bibliothek. Die entsprechenden Librarys müssen also beim Aufruf des Compilers angegeben werden:

g++ -lboost_system-mt -lboost_thread-mt -o minicom minicom.cpp

Hier mein modifizierter Code:

/* minicom.cpp
 *      A simple demonstration minicom client with Boost asio
 *
 *      Parameters: baud port
 *      To end the application, send Ctrl-C on standard input
 *
 * Info
 *   http://boost.2283326.n4.nabble.com/Simple-serial-port-demonstration-with-boost-asio-asynchronous-I-O-td2582657.html
 *
 * Compile:
 *   g++ -lboost_system-mt -lboost_thread-mt -o minicom minicom.cpp
 *
 * Author:
 *   Jeff Gray-3 (http://boost.2283326.n4.nabble.com/Simple-serial-port-demonstration-with-boost-asio-asynchronous-I-O-td2582657.html)
 *
 *   modified by Heiner Otterstedt (maheo.eu)
 */

#include <deque>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
#include <boost/thread.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>

#ifdef POSIX
#include <termios.h>
#endif

using namespace std;

class minicom_client
{
public:
  minicom_client(boost::asio::io_service& io_service, unsigned int baud, const string& device)
                : active_(true),
                  io_service_(io_service),
                  serialPort(io_service, device)
  { if (!serialPort.is_open())
    { cerr << "Failed to open serial port\n";
      return;
    }
    boost::asio::serial_port_base::baud_rate baud_option(baud);
    serialPort.set_option(baud_option); // set the baud rate after the port has been opened
    read_start();
  }

  void write(const char msg) // pass the write data to the do_write function via the io service in the other thread
  { io_service_.post(boost::bind(&minicom_client::do_write, this, msg));
  }

  void write(const string s)
  { int i;

    for(i=0; i<s.length(); i++)
      { write((const char)s.at(i));
    }

  }

  void close() // call the do_close function via the io service in the other thread
  { io_service_.post(boost::bind(&minicom_client::do_close, this, boost::system::error_code()));
  }

  bool active() // return true if the socket is still active
  { return active_;
  }

private:

  static const int max_read_length = 512; // maximum amount of data to read in one operation

  void read_start(void)
  { // Start an asynchronous read and call read_complete when it completes or fails
    serialPort.async_read_some(boost::asio::buffer(read_msg_, max_read_length),
                        boost::bind(&minicom_client::read_complete,
                                this,
                                boost::asio::placeholders::error,
                                boost::asio::placeholders::bytes_transferred));
  }

  void read_complete(const boost::system::error_code& error, size_t bytes_transferred)
  { // the asynchronous read operation has now completed or failed and returned an error
    if (!error)
    { // read completed, so process the data
      cout.write(read_msg_, bytes_transferred); // echo to standard output
      read_start(); // start waiting for another asynchronous read again
    }
    else
      do_close(error);
  }

  void do_write(const char msg)
  { // callback to handle write call from outside this class
    bool write_in_progress = !write_msgs_.empty(); // is there anything currently being written?
    write_msgs_.push_back(msg); // store in write buffer
    if (!write_in_progress) // if nothing is currently being written, then start
      write_start();
  }

  void write_start(void)
  { // Start an asynchronous write and call write_complete when it completes or fails
    boost::asio::async_write(serialPort,
                        boost::asio::buffer(&write_msgs_.front(), 1),
                        boost::bind(&minicom_client::write_complete,
                                this,
                                boost::asio::placeholders::error));
  }

  void write_complete(const boost::system::error_code& error)
  { // the asynchronous read operation has now completed or failed and returned an error
    if (!error)
    { // write completed, so send next write data
      write_msgs_.pop_front(); // remove the completed data
      if (!write_msgs_.empty()) // if there is anthing left to be written
        write_start(); // then start sending the next item in the buffer
    }
    else
      do_close(error);
  }

  void do_close(const boost::system::error_code& error)
  { // something has gone wrong, so close the socket & make this object inactive
    if (error == boost::asio::error::operation_aborted) // if this call is the result of a timer cancel()
       return; // ignore it because the connection cancelled the timer

    if (error)
       cerr << "Error: " << error.message() << endl; // show the error message
    else
       cout << "Error: Connection did not succeed.\n";

    cout << "Press Enter to exit\n";
    serialPort.close();
    active_ = false;
  }

private:
  bool active_; // remains true while this object is still operating
  boost::asio::io_service& io_service_; // the main IO service that runs this connection
  boost::asio::serial_port serialPort; // the serial port this instance is connected to
  char read_msg_[max_read_length]; // data read from the socket
  deque<char> write_msgs_; // buffered write data
};

int main(int argc, char* argv[])
{
// on Unix POSIX based systems, turn off line buffering of input, so cin.get() returns after every keypress
// On other systems, you'll need to look for an equivalent
#ifdef POSIX
  termios stored_settings;
  tcgetattr(0, &stored_settings);
  termios new_settings = stored_settings;
  new_settings.c_lflag &= (~ICANON);
  new_settings.c_lflag &= (~ISIG); // don't automatically handle control-C
  tcsetattr(0, TCSANOW, &new_settings);
#endif
  try
  {
    if (argc != 3)
    {
      cerr << "Usage: minicom <baud> <device>\n";
      return 1;
    }
    boost::asio::io_service io_service;

    // define an instance of the main class of this program
    minicom_client c(io_service, boost::lexical_cast<unsigned int>(argv[1]), argv[2]);

    // run the IO service as a separate thread, so the main thread can block on standard input
    boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));

    while (c.active()) // check the internal state of the connection to make sure it's still running
    { char ch;
      cin.get(ch); // blocking wait for standard input
      if (ch == 3) // ctrl-C to end program
         break;

      // Testing strings...
      if(ch == '1')      { string s = "eins"; c.write(s); }
      else if(ch == '2') { string s = "zwei"; c.write(s); }
      else if(ch == '3') { string s = "drei"; c.write(s); }
      else if(ch == '4') { string s = "vier"; c.write(s); }
      else if(ch == '5') { string s = "fünf"; c.write(s); }
      else               {                    c.write(ch); }
    }
    c.close(); // close the minicom client connection
    t.join(); // wait for the IO service thread to close
  }
  catch (exception& e)
  { cerr << "Exception: " << e.what() << "\n";
  }
#ifdef POSIX // restore default buffering of standard input
  tcsetattr(0, TCSANOW, &stored_settings);
#endif
  return 0;
}

Das fertige Programm wird mit den Parametern Baudrate und Port aufgerufen, also zum Beispiel

minicom 9600 /dev/ttyUSB0

für einen Arduino Duemilanove oder

minicom 9600 /dev/ttyACM0

für einen Arduino Uno.

Hinterlassen Sie eine Antwort

 

 

 

Sie können diese HTML Tags verwenden

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

eMail-Benachrichtigung bei weiteren Kommentaren.
Auch möglich: Abo ohne Kommentar.