Linux 通过串行连接的双向 C++ 通信

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11677639/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 13:55:48  来源:igfitidea点击:

Two-way C++ communication over serial connection

c++linuxserial-portarduino

提问by learnvst

I am trying to write a really simple C++ application to communicate with an Arduino. I would like to send the Arduino a character that it sends back immediately. The Arduino code that I took from a tutorial looks like this:

我正在尝试编写一个非常简单的 C++ 应用程序来与 Arduino 通信。我想向 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);
}

I can communicate with the Arduino easily using GNU screen, so I know that everything is working fine with the basic communication:

我可以使用 GNU 屏幕轻松地与 Arduino 通信,所以我知道基本通信一切正常:

$ screen /dev/tty.usbmodem641 9600

$屏幕/dev/tty.usbmodem641 9600

The (broken) C++ code that I have looks like this:

我拥有的(损坏的)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;
}

It compiles fine, but when running it, some lights flash on the Arduino and the terminal just hangs at:

它编译得很好,但是在运行它时,Arduino 上的一些灯闪烁,终端挂在:

Opening fstream

打开 fstream

Where am I going wrong?

我哪里错了?

回答by B?ови?

You should check if you have access to /dev/tty.usbmodem641. The usual way in Linux is to add the user to the proper group with adduser.

您应该检查您是否有权访问/dev/tty.usbmodem641. Linux 中通常的方法是将用户添加到适当的组中adduser

By the way, I know that to access the serial port, one needs to open /dev/ttyS0(for COM1), until /dev/ttyS3. See for example this example in C.

顺便说一句,我知道要访问串行端口,需要打开/dev/ttyS0(对于 COM1),直到/dev/ttyS3. 例如,请参阅C 中的此示例

回答by A.H.

There are three points:

有以下三点:

First:You don't initialize the serial port (TTY) on the Linux side. Nobody knows in what state it is.

第一:你没有在 Linux 端初始化串口(TTY)。没有人知道它处于什么状态。

Doing this in your program you must use tcgetattr(3)and tcsetattr(3). You can find the required parameters by using these keywords at this site, the Arduino site or on Google. But just for quick testingI propose to issue this command before you call your own command:

在您的程序中执行此操作,您必须使用tcgetattr(3)tcsetattr(3)。您可以在此站点、Arduino 站点或 Google 上使用这些关键字找到所需的参数。但为了快速测试,我建议在您调用自己的命令之前发出此命令:

stty -F /dev/tty.usbmodem641 sane raw pass8 -echo -hupcl clocal 9600

Especially the the missing clocalmight prevent you opening the TTY.

尤其是丢失clocal可能会阻止您打开 TTY。

Second:When the device is open, you should wait a little before sending anything. By default the Arduino resets when the serial line is opened or closed. You have to take this into account.

第二:当设备打开时,你应该等待一段时间再发送任何东西。默认情况下,Arduino 在串行线路打开或关闭时重置。你必须考虑到这一点。

The -hupclpart will prevent this reset most of the time. But at least one reset is always necessary, because -hupclcan be set only when the TTY is already open and at that time the Arduino has received the reset signal already. So -hupclwill "only" prevent future resets.

-hupcl大多数情况下,该部件将阻止这种重置。但是至少需要进行一次复位,因为-hupcl只有在 TTY 已经打开并且那时 Arduino 已经收到复位信号时才能进行设置。所以-hupcl将“仅”防止未来的重置。

Third:There is NOerror handling in your code. Please add code after each IO operation on the TTY which checks for errors and - the most important part - prints helpful error messages using perror(3)or similar functions.

第三:错误代码处理。请在 TTY 上的每个 IO 操作之后添加代码,用于检查错误和 - 最重要的部分 - 使用perror(3)或类似功能打印有用的错误消息。

回答by learnvst

I found a nice example by Jeff Gray of how to make a simple minicom type client using boost::asio. The original code listing can be found on the boost user group. This allows connection and communication with the Arduino like in the GNU Screen example mentioned in the original post.

我找到了 Jeff Gray 的一个很好的例子,说明了如何使用boost::asio. 原始代码清单可以在 boost 用户组中找到。这允许像在原始帖子中提到的 GNU Screen 示例中一样与 Arduino 连接和通信。

The code example (below) needs to be linked with the following linker flags

代码示例(下面)需要与以下链接器标志链接

-lboost_system-mt -lboost_thread-mt

-lboost_system-mt -lboost_thread-mt

...but with a bit of tweaking, some of the dependence on boost can be replaced with new C++11 standard features. I'll post revised versions as and when I get around to it. For now, this compiles and is a solid basis.

...但是通过一些调整,可以用新的 C++11 标准特性取代对 boost 的一些依赖。当我解决它时,我会发布修订版。目前,这是编译并且是一个坚实的基础。

/* 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; 
}