首页 > 解决方案 > C ++串行通信读取数据有效但写入失败

问题描述

我正在为嵌入式系统和在 Linux 环境中运行的 C++ 应用程序之间的串行通信创建一个类。因此,我使用了适用于 Linux 的 termios API,在此处进行了描述。

构造函数将打开设备的串口。在我的情况下,我使用的 arduino 微控制器是“ttyUSB0”。接下来它将设置波特率和其他端口选项。

我还添加了在串行端口上读取或写入数据的功能。因为 read 是一个阻塞函数(在接收到数据或超时后才返回),我添加了一个函数来检查是否有可用的字节,您可以在调用“Read()”之前应该这样做。

制作测试用例后,阅读似乎工作正常。函数“Available()”确实返回了可用的字节数。阅读后将它们打印到控制台。

但是,由于某些未知原因,我的写入功能不起作用,即使我“相信”我正确地遵循了指南中的步骤。我为 write 功能做了一个测试用例:一旦收到正确的消息,arduino 应该闪烁它的内置 LED。当一条消息以开始标记“#”开始并以结束标记“$”结束时,它是正确的。

当我使用测试工具 putty 或 arduino 的串行监视器发送正确的消息时,LED 会闪烁。但是当我通过自己的 write 函数发送消息时,这不会发生。

arduino 有其他内置 LED,用于指示 RX 和 TX 引脚上的数据。一旦我从自己的写入函数发送数据,这些 LED 确实会亮起,但我的测试用例中的闪烁函数从未被调用。然后我检查是否读取了任何字节,但是当从我自己的写入函数发送数据时,arduino 的“Serial.available()”永远不会返回高于 0 的值。

我认为这个错误要么在写函数本身,要么在串口的配置中。到目前为止,我无法弄清楚这一点。有没有人对此有任何经验或知识,或者对我应该如何解决这个问题有任何提示?

提前致谢,

短剑

Linux 代码:

主文件

#include "serial.h"
#include <iostream>

using namespace std;

int main()
{
    //TEST CASE FOR WRITING DATA
    Serial serial("/dev/ttyUSB0");
    serial.Write("#TEST$"); 

    //TEST CASE FOR READING DATA
    /*while (true)
    {
        char message[100];
        char * ptr = NULL;
        while (serial.Available() > 0)
        {
            char c; 
            serial.Read(&c);
            switch(c)
            {
            case '#':
                ptr = message;
                break;
            case '$':
                if (ptr != NULL)
                {
                    *ptr = '\0';
                }
                std::cout << "received: " << message << std::endl;
                ptr = NULL;
                break;
            default:
                 if (ptr != NULL)
                {
                    *ptr = c;
                    ptr++;
                }
                break;
            }
        }
    }*/
    return EXIT_SUCCESS;
}

串行.h

#ifndef SERIAL_H
#define SERIAL_H

#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <string>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>

class Serial
{
    private:
        int fd;
    public:
        Serial(std::string device);

        ~Serial()
        {
            close(fd);
        };     

        int Available();
        void Read(char * buffer, int amountOfBytes);
        void Read(char * bytePtr);
        int Write(std::string message);
};

#endif

串口.cpp

#include "serial.h"
#include <stdexcept>
#include <string.h>

Serial::Serial(std::string device)
{   
    // Open port
    fd = open(device.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd < 0)
    {
        throw std::runtime_error("Failed to open port!");
    }

    // Config
    struct termios config;

    tcgetattr(fd, &config);

    // Set baudrate
    cfsetispeed(&config, B9600);
    cfsetospeed(&config, B9600);

    // 9600 8N1
    config.c_cflag &= ~PARENB;
    config.c_cflag &= ~CSTOPB;
    config.c_cflag &= ~CSIZE;
    config.c_cflag |=  CS8;

    // Disable hardware based flow control
    config.c_cflag &= ~CRTSCTS;

    // Enable receiver
    config.c_cflag |= CREAD | CLOCAL;                               

    // Disable software based flow control
    config.c_iflag &= ~(IXON | IXOFF | IXANY);

    // Termois Non Cannoincal Mode 
    config.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); 

    // Minimum number of characters for non cannoincal read
    config.c_cc[VMIN]  = 1;

    // Timeout in deciseconds for read
    config.c_cc[VTIME] = 0; 

    // Save config
    if (tcsetattr(fd, TCSANOW, &config) < 0)                        
    {
        close(fd);
        throw std::runtime_error("Failed to configure port!");
    }

    // Flush RX Buffer
    if (tcflush(fd, TCIFLUSH) < 0)
    {
        close(fd);
        throw std::runtime_error("Failed to flush buffer!");
    }
}

int Serial::Available()
{
    int bytes = 0;
    if (ioctl(fd, TIOCINQ, &bytes) < 0)
    {
        close(fd);
        throw std::runtime_error("Failed to check buffer!");
    }
    return bytes;
}

void Serial::Read(char * buffer, int amountOfBytes)
{
    if (read(fd, buffer, amountOfBytes) < 0)
    {
        close(fd);
        throw std::runtime_error("Failed to read bytes!");
    }
}

void Serial::Read(char * bytePtr)
{
    return Serial::Read(bytePtr, 1);
}

int Serial::Write(std::string message)
{
    int length = message.size();
    if (length > 100)
    {
        throw std::invalid_argument("Message may not be longer than 100 bytes!");
    }

    char msg[101];
    strcpy(msg, message.c_str());

    int bytesWritten = write(fd, msg, length);

    if (bytesWritten < 0)
    {
        close(fd);
        throw std::runtime_error("Failed to write bytes!");
    }

    return bytesWritten;
}

Arduino代码

void setup() 
{
    Serial.begin(9600);
    pinMode(LED_BUILTIN, OUTPUT);
}

void loop() 
{
    //TEST-CASE FOR WRITING DATA
    /*Serial.print("#TEST$");
    delay(1000);*/

    //TEST-CASE FOR READING DATA
    char message[100];
    char * ptr = NULL;
    while (Serial.available() > 0)
    {
        char c = Serial.read();
        switch(c)
        {
        case '#':
            ptr = message;
            break;
        case '$':
            if (ptr != NULL)
            {
                *ptr = '\0';
            }
            ptr = NULL;
            int messageLength = strlen(message);
            Blink();
            break;
        default:
            if (ptr != NULL)
            {
              *ptr = c;
              ptr++;
            }
            break;
        }
    }
}

void Blink()
{
    digitalWrite(LED_BUILTIN, HIGH);
    delay(1000);
    digitalWrite(LED_BUILTIN, LOW);
    delay(1000);
}

标签: linuxserial-portfile-descriptortermiosfcntl

解决方案


解决了。“打开”功能在串行端口上发送一个信号,arduino 将其解释为重新启动的信号。我通过禁用 auto-reset解决了这个问题。

或者,您可以在保存配置后添加两秒钟的延迟。

这个问题特别适用于 arduino 微控制器。


推荐阅读