Arduinoと通信するための本当にシンプルなC++アプリケーションを書こうとしています。 Arduinoにすぐに返送するキャラクターを送りたいのですが。チュートリアルから取得したArduinoコードは次のようになります。
void setup()
{
Serial.begin(9600);
}
void loop()
{
//Have the Arduino wait to receive input
while (Serial.available()==0);
//Read the input
char val = Serial.read();
//Echo
Serial.println(val);
}
GNU screenを使用して、Arduinoと簡単に通信できるので、基本的な通信ですべてが正常に機能していることがわかります。
$ screen /dev/tty.usbmodem641 9600
私が持っている(壊れた)C++コードは次のようになります:
#include <fstream>
#include <iostream>
int main()
{
std::cout << "Opening fstream" << std::endl;
std::fstream file("/dev/tty.usbmodem641");
std::cout << "Sending integer" << std::endl;
file << 5 << std::endl; // endl does flush, which may be important
std::cout << "Data Sent" << std::endl;
std::cout << "Awaiting response" << std::endl;
std::string response;
file >> response;
std::cout << "Response: " << response << std::endl;
return 0;
}
正常にコンパイルされますが、実行すると、Arduinoでいくつかのライトが点滅し、ターミナルが次の場所でハングします。
Fstreamを開く
どこが間違っているのですか?
3つのポイントがあります:
First: Linux側でシリアルポート(TTY)を初期化しません。それがどのような状態にあるのか誰も知りません。
プログラムでこれを行うには、tcgetattr(3)
とtcsetattr(3)
を使用する必要があります。このサイト、Arduinoサイト、またはGoogleでこれらのキーワードを使用して、必要なパラメーターを見つけることができます。しかし、簡単なテストのために独自のコマンドを呼び出す前に、このコマンドを発行することを提案します。
_stty -F /dev/tty.usbmodem641 sane raw pass8 -echo -hupcl clocal 9600
_
特に、clocal
がない場合は、TTYを開くことができない可能性があります。
Second:デバイスが開いているときは、何かを送信する前に少し待つ必要があります。デフォルトでは、Arduinoはシリアルラインが開いたり閉じたりするとリセットされます。これを考慮に入れる必要があります。
_-hupcl
_部分は、ほとんどの場合、このリセットを防ぎます。ただし、_-hupcl
_はTTYがすでに開いていて、その時点でArduinoがリセット信号を受信している場合にのみ設定できるため、少なくとも1回のリセットが常に必要です。したがって、_-hupcl
_は将来のリセットを「のみ」防止します。
Third:コードに[〜#〜] no [〜#〜]エラー処理があります。エラーをチェックし、最も重要な部分であるTTYの各IO操作の後に、perror(3)
または同様の関数を使用して役立つエラーメッセージを出力するコードを追加してください。
boost::asio
を使用して単純なミニコムタイプのクライアントを作成する方法のJeffGrayによる素晴らしい例を見つけました。元の コードリストはブーストユーザーグループにあります 。これにより、元の投稿で言及されているGNU画面の例のように、Arduinoとの接続と通信が可能になります。
コード例(以下)は、次のリンカーフラグとリンクする必要があります
-lboost_system-mt -lboost_thread-mt
...しかし、少し調整することで、ブーストへの依存の一部を新しいC++ 11標準機能に置き換えることができます。改訂版は、入手次第、投稿します。今のところ、これはコンパイルされ、確固たる基盤です。
/* minicom.cpp
A simple demonstration minicom client with Boost asio
Parameters:
baud rate
serial port (eg /dev/ttyS0 or COM1)
To end the application, send Ctrl-C on standard input
*/
#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 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;
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;
}
/dev/tty.usbmodem641
にアクセスできるかどうかを確認する必要があります。 Linuxでの通常の方法は、adduser
を使用してユーザーを適切なグループに追加することです。
ちなみに、シリアルポートにアクセスするには、/dev/ttyS0
まで/dev/ttyS3
(COM1の場合)を開く必要があることを私は知っています。たとえば、 Cのこの例 を参照してください。