【问题标题】:init, read and write for linux serial device with C用C初始化,读写linux串行设备
【发布时间】:2025-12-04 21:20:04
【问题描述】:

我正在开发一个新项目,我想与连接到我的 debian 机器的 FTDI 建立连接。我打算用 C 而不是 C++ 编写代码。这就是我的问题。我发现的所有示例都不完整,或者是为 c++ 编译器而不是 GCC 编译器制作的。

目标是与连接到 FTDI 的微控制器通信。为了调试,我想开始构建一个能够:

  • 在启动时使用 ttyUSB1 初始化串行连接
  • 发送一个字符串
  • 电脑接收到的字符串显示
  • 将通信保存为 .txt 文件

是否有任何示例代码或教程可以做到这一点?

如果我成功了,我会将代码放在这里,以便新观众可以使用它!

编辑:

就像我说的,如果我有代码,我会发布代码,这对我有用:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

#define MODEM "/dev/ttyUSB0"
#define BAUDRATE B115200    

int main(int argc,char** argv)
{   
    struct termios tio;
    struct termios stdio;
    struct termios old_stdio;
    int tty_fd, flags;
    unsigned char c='D';
    tcgetattr(STDOUT_FILENO,&old_stdio);
    printf("Please start with %s /dev/ttyS1 (for example)\n",argv[0]);
    memset(&stdio,0,sizeof(stdio));
    stdio.c_iflag=0;
    stdio.c_oflag=0;
    stdio.c_cflag=0;
    stdio.c_lflag=0;
    stdio.c_cc[VMIN]=1;
    stdio.c_cc[VTIME]=0;
    tcsetattr(STDOUT_FILENO,TCSANOW,&stdio);
    tcsetattr(STDOUT_FILENO,TCSAFLUSH,&stdio);
    fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);       // make the reads non-blocking
    memset(&tio,0,sizeof(tio));
    tio.c_iflag=0;
    tio.c_oflag=0;
    tio.c_cflag=CS8|CREAD|CLOCAL;           // 8n1, see termios.h for more information
    tio.c_lflag=0;
    tio.c_cc[VMIN]=1;
    tio.c_cc[VTIME]=5;
    if((tty_fd = open(MODEM , O_RDWR | O_NONBLOCK)) == -1){
        printf("Error while opening\n"); // Just if you want user interface error control
        return -1;
    }
    cfsetospeed(&tio,BAUDRATE);    
    cfsetispeed(&tio,BAUDRATE);            // baudrate is declarated above
    tcsetattr(tty_fd,TCSANOW,&tio);
    while (c!='q'){
        if (read(tty_fd,&c,1)>0){
            write(STDOUT_FILENO,&c,1); // if new data is available on the serial port, print it out
            printf("\n");
        }
        if (read(STDIN_FILENO,&c,1)>0){
            write(tty_fd,&c,1);//if new data is available on the console, send it to serial port
            printf("\n");
        }
    }
    close(tty_fd);
    tcsetattr(STDOUT_FILENO,TCSANOW,&old_stdio);
    return EXIT_SUCCESS;
}

大部分代码来自http://en.wikibooks.org/wiki/Serial_Programming/Serial_Linux,但我也使用了下面发布的代码中的一部分。

【问题讨论】:

  • 这个问题你查了吗*.com/questions/2982552/…
  • minicom 出了什么问题?
  • 好问题,稍后我想重建程序以记录和执行命令,而不需要我在电脑后面,所以这个概念只是开始。
  • 我明白了。好吧,你至少可以看看 minicom 的源代码(alioth.debian.org/frs/…)。它是开源的,用 C 语言编写,也被大量使用多年,所以我确信有很多东西可以重用/学习。

标签: c linux serial-port tty


【解决方案1】:

处理串口(适用于 linux 操作系统):
- 要打开通信,您需要一个描述符,该描述符将是您的串行端口的句柄。
- 设置标志以控制通信方式。
- 将命令写入此句柄(确保正确格式化输入)。
- 得到答案。 (确保您要阅读所需的信息量)
- 关闭把手。 它看起来像这样:

int fd; // file descriptor
int flags; // communication flags
int rsl_len; // result size
char message[128]; // message to send, you can even dinamically alocate.
char result[128]; // result to read, same from above, thanks to @Lu

flags = O_RDWR | O_NOCTTY; // Read and write, and make the job control for portability
if ((fd = open("/dev/ttyUSB1", flags)) == -1 ) {
  printf("Error while opening\n"); // Just if you want user interface error control
  return -1;
}
// In this point your communication is already estabilished, lets send out something
strcpy(message, "Hello");
if (rsl_len = write(fd, message, strlen(message)) < 0 ) {
  printf("Error while sending message\n"); // Again just in case
  return -2;
}
if (rsl_len = read(fd, &result, sizeof(result)) < 0 ) {
  printf("Error while reading return\n");
  return -3;
}
close(fd);

请注意,您必须关心写什么和读什么。 在奇偶校验控制、停止位、波特率等情况下可以使用更多标志。

【讨论】:

  • 当然这段代码不处理E_INTR,不使用O_CLOEXEC打开文件句柄,不处理信号......这不是你想告诉新手的方式编写好的 Unix 代码。一旦你把它变成了好的 Unix 代码,它所做的一点点就会很长,有一个问题是它是否值得在纯 C 中再做而不失去你的理智......
  • @KubaOber 在光谱的另一端,可以说 Qt 是如此庞大且包罗万象,以至于它是否值得使用存在一个问题。我已经看到 Qt 是作为对项目的依赖而引入的,这些项目仅将它用于一小部分功能(例如:基本数据结构,使用 STL 实现的效果要好得多),基本上你付出的代价是膨胀。我认为这样做更容易保持你的理智......它是从头开始制造你的汽车,而不是让制造商摆布更换零件
  • 我可能选择了一个糟糕的类比,因为没有非凡的资源,你最终得到的汽车将不适合使用。不过,POSIX 的挑战性要小一些,主要是因为它有据可查。
  • @StevenLu Qt 非常有用,即使您不引入整个模块,而只是选择其中的一部分。如果您将 Qt 库静态链接到可执行文件,这通常会自动完成。作为额外的奖励,您当然可以在项目中只包含某些 Qt source 文件,就像 Qt 在引导自身时所做的那样。在 Windows 上,在 cstdio 的 stdout 上使用 QString、QTextStream 的完全静态编译的 hello world,不支持线程,不依赖于任何运行时,大约 800kb 不费力。
  • @rfermi 我很确定messageresult 需要足够大char[]s。
【解决方案2】:

由于 gcc 是一个 C/C++ 编译器,您无需将自己限制为纯 C。

如果您喜欢编写大量样板代码,并且您真的知道自己在做什么,那么坚持使用纯 C 是可以的。许多人错误地使用了 Unix API,而且很多示例代码都过于简单化了。至少可以说,编写正确的 Unix C 代码有点烦人。

就个人而言,我建议不仅使用 C++,还建议使用 Qt 等更高级别的应用程序开发框架。 Qt 5 捆绑了一个QtSerialPort 模块,可以轻松枚举串行端口、配置它们以及将数据输入和输出它们。 Qt 不会强制你使用 gui 模块,它可以是命令行应用程序,也可以是非交互式服务器/守护进程。

QtSerialPort 也可以在 Qt 4 中使用,但它没有与 Qt 4 捆绑在一起,您必须将其添加到您的项目中。我建议从 Qt 5 开始,使用它的 C++11 支持会更好。

使用 Qt 的代码可以非常简单,不会比您的纯英文描述长多少。下面是一个使用 Qt 5 和 C++11 的 Qt 控制台应用程序。它使用coreserialport 模块。它也是handles the SIGINT signal,以便在进程因^C 而终止之前刷新输出文件。我正在使用 QLocalSocket 代替原始 Unix API 从 Unix 信号处理程序进行通信,功能是相同的。

只有main中的代码是严格要求的,其余的只是在你点击^C时让它正确包装。

#include <QCoreApplication>
#include <QSerialPort>
#include <QFile>
#include <QTextStream>
#include <QLocalServer>
#include <QLocalSocket>
#include <cstdio>
#include <csignal>

QLocalSocket * xmit;

static void signalHandler(int)
{
    xmit->write(" ");
    xmit->flush();
}

static bool setupSignalHandler()
{
    QLocalServer srv;
    srv.listen("foobarbaz");
    xmit = new QLocalSocket(qApp);
    xmit->connectToServer(srv.serverName(), QIODevice::WriteOnly);
    srv.waitForNewConnection();
    QLocalSocket * receive = srv.nextPendingConnection();
    receive->setParent(qApp);
    qApp->connect(receive, &QLocalSocket::readyRead, &QCoreApplication::quit);
    struct sigaction sig;
    sig.sa_handler = signalHandler;
    sigemptyset(&sig.sa_mask);
    sig.sa_flags = SA_RESTART;
    return ! sigaction(SIGINT, &sig, NULL);
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    setupSignalHandler();

    QSerialPort port("ttyUSB1");
    QFile file("file.txt");
    QTextStream err(stderr, QIODevice::WriteOnly);
    QTextStream out(stdout, QIODevice::WriteOnly);
    if (!file.open(QIODevice::WriteOnly)) {
        err << "Couldn't open the output file" << endl;
        return 1;
    }
    if (!port.open(QIODevice::ReadWrite)) {
        err << "Couldn't open the port" << endl;
        return 2;
    }
    port.setBaudRate(9600);
    QObject::connect(&port, &QSerialPort::readyRead, [&](){
        QByteArray data = port.readAll();
        out << data;
        file.write(data);
    });
    out << "Use ^C to quit" << endl;
    return a.exec();
}

【讨论】:

  • 听起来很有希望,但如何将 Qt 库添加到 GCC?我刚刚安装了QT5,但编译时仍然出现“gcc -o qtSerial qtSerial.cpp qtSerial.cpp:1:28: fatal error: QCoreApplication: No such file or directory”。
  • 好吧,如果不了解发生了什么,您无法手动执行此操作。使用 QT creator 创建项目,将文件添加到项目中,将 serialport 模块添加到 .pro 文件 (QT += serialport),它应该可以工作了。
  • 要求的语言是 C,操作系统是 unix。否决了这两个要求,因为这两个要求没有得到遵守。
  • @lesto 我提供了一个替代解决方案,这肯定适用于任何可以编译 Qt 的 Unix 系统(基本上任何有价值的东西):)