在 Linux 上用 C 语言读写串口

Posted

技术标签:

【中文标题】在 Linux 上用 C 语言读写串口【英文标题】:Reading and writing to serial port in C on Linux 【发布时间】:2013-08-09 03:46:01 【问题描述】:

我正在尝试使用 FTDI 通过 USB 端口发送/接收数据,因此我需要使用 C/C++ 处理串行通信。我正在使用 Linux (Ubuntu)。

基本上,我连接到一个正在侦听传入命令的设备。我需要发送这些命令并读取设备的响应。命令和响应都是 ASCII 字符

使用 GtkTerm 一切正常,但是当我切换到 C 编程时,我遇到了问题。

这是我的代码:

#include <stdio.h>      // standard input / output functions
#include <stdlib.h>
#include <string.h>     // string function definitions
#include <unistd.h>     // UNIX standard function definitions
#include <fcntl.h>      // File control definitions
#include <errno.h>      // Error number definitions
#include <termios.h>    // POSIX terminal control definitions

/* Open File Descriptor */
int USB = open( "/dev/ttyUSB0", O_RDWR| O_NONBLOCK | O_NDELAY );

/* Error Handling */
if ( USB < 0 )

cout << "Error " << errno << " opening " << "/dev/ttyUSB0" << ": " << strerror (errno) << endl;


/* *** Configure Port *** */
struct termios tty;
memset (&tty, 0, sizeof tty);

/* Error Handling */
if ( tcgetattr ( USB, &tty ) != 0 )

cout << "Error " << errno << " from tcgetattr: " << strerror(errno) << endl;


/* Set Baud Rate */
cfsetospeed (&tty, B9600);
cfsetispeed (&tty, B9600);

/* Setting other Port Stuff */
tty.c_cflag     &=  ~PARENB;        // Make 8n1
tty.c_cflag     &=  ~CSTOPB;
tty.c_cflag     &=  ~CSIZE;
tty.c_cflag     |=  CS8;
tty.c_cflag     &=  ~CRTSCTS;       // no flow control
tty.c_lflag     =   0;          // no signaling chars, no echo, no canonical processing
tty.c_oflag     =   0;                  // no remapping, no delays
tty.c_cc[VMIN]      =   0;                  // read doesn't block
tty.c_cc[VTIME]     =   5;                  // 0.5 seconds read timeout

tty.c_cflag     |=  CREAD | CLOCAL;     // turn on READ & ignore ctrl lines
tty.c_iflag     &=  ~(IXON | IXOFF | IXANY);// turn off s/w flow ctrl
tty.c_lflag     &=  ~(ICANON | ECHO | ECHOE | ISIG); // make raw
tty.c_oflag     &=  ~OPOST;              // make raw

/* Flush Port, then applies attributes */
tcflush( USB, TCIFLUSH );

if ( tcsetattr ( USB, TCSANOW, &tty ) != 0)

cout << "Error " << errno << " from tcsetattr" << endl;


/* *** WRITE *** */

unsigned char cmd[] = 'I', 'N', 'I', 'T', ' ', '\r', '\0';
int n_written = write( USB, cmd, sizeof(cmd) -1 );

/* Allocate memory for read buffer */
char buf [256];
memset (&buf, '\0', sizeof buf);

/* *** READ *** */
int n = read( USB, &buf , sizeof buf );

/* Error Handling */
if (n < 0)

     cout << "Error reading: " << strerror(errno) << endl;


/* Print what I read... */
cout << "Read: " << buf << endl;

close(USB);

发生的情况是read() 返回 0(根本没有读取字节)或阻塞直到超时(VTIME)。我假设发生这种情况是因为write() 没有发送任何内容。在这种情况下,设备不会收到命令,我也无法收到响应。事实上,在我的程序被阻止读取时关闭设备实际上成功地获得了响应(设备在关闭时发送了一些东西)。

奇怪的是添加了这个

cout << "I've written: " << n_written << "bytes" << endl; 

write() 通话后,我收到:

I've written 6 bytes

这正是我所期望的。只有我的程序无法正常工作,例如 我的设备无法接收我实际在端口上写入的内容

我尝试了不同的方法和解决方案,也涉及数据类型(我尝试使用 std::string,例如 cmd = "INIT \r"const char)但没有任何效果。

谁能告诉我哪里错了?

提前谢谢你。

编辑: 此代码的先前版本使用了

unsigned char cmd[] = "INIT \n"

还有cmd[] = "INIT \r\n"。我更改了它,因为我的设备的命令 sintax 报告为

&lt;command&gt;&lt;SPACE&gt;&lt;CR&gt;

我也尝试在阅读时避免使用O_NONBLOCK 标志,但我只会一直阻塞到永远。我试过使用select(),但没有任何反应。只是为了尝试,我创建了一个等待循环,直到数据可用,但我的代码永远不会退出循环。顺便说一句,等待或usleep() 是我需要避免的。报告的只是我的代码的摘录。 完整的代码需要在实时环境中工作(特别是 OROCOS),所以我真的不想要类似睡眠的功能。

【问题讨论】:

std::cout 是 C++。一般来说,您的MCVE 应该坚持使用一种语言,除非您怀疑交互不好。否则有些人开始争论 C++ 不是 C。 【参考方案1】:

我已经解决了我的问题,所以我在这里发布正确的代码,以防有人需要类似的东西。

开放端口

int USB = open( "/dev/ttyUSB0", O_RDWR| O_NOCTTY );

设置参数

struct termios tty;
struct termios tty_old;
memset (&tty, 0, sizeof tty);

/* Error Handling */
if ( tcgetattr ( USB, &tty ) != 0 ) 
   std::cout << "Error " << errno << " from tcgetattr: " << strerror(errno) << std::endl;


/* Save old tty parameters */
tty_old = tty;

/* Set Baud Rate */
cfsetospeed (&tty, (speed_t)B9600);
cfsetispeed (&tty, (speed_t)B9600);

/* Setting other Port Stuff */
tty.c_cflag     &=  ~PARENB;            // Make 8n1
tty.c_cflag     &=  ~CSTOPB;
tty.c_cflag     &=  ~CSIZE;
tty.c_cflag     |=  CS8;

tty.c_cflag     &=  ~CRTSCTS;           // no flow control
tty.c_cc[VMIN]   =  1;                  // read doesn't block
tty.c_cc[VTIME]  =  5;                  // 0.5 seconds read timeout
tty.c_cflag     |=  CREAD | CLOCAL;     // turn on READ & ignore ctrl lines

/* Make raw */
cfmakeraw(&tty);

/* Flush Port, then applies attributes */
tcflush( USB, TCIFLUSH );
if ( tcsetattr ( USB, TCSANOW, &tty ) != 0) 
   std::cout << "Error " << errno << " from tcsetattr" << std::endl;

unsigned char cmd[] = "INIT \r";
int n_written = 0,
    spot = 0;

do 
    n_written = write( USB, &cmd[spot], 1 );
    spot += n_written;
 while (cmd[spot-1] != '\r' && n_written > 0);

绝对没有必要逐字节写入,int n_written = write( USB, cmd, sizeof(cmd) -1) 也可以正常工作。

最后,阅读

int n = 0,
    spot = 0;
char buf = '\0';

/* Whole response*/
char response[1024];
memset(response, '\0', sizeof response);

do 
    n = read( USB, &buf, 1 );
    sprintf( &response[spot], "%c", buf );
    spot += n;
 while( buf != '\r' && n > 0);

if (n < 0) 
    std::cout << "Error reading: " << strerror(errno) << std::endl;

else if (n == 0) 
    std::cout << "Read nothing!" << std::endl;

else 
    std::cout << "Response: " << response << std::endl;

这个对我有用。谢谢大家!

【讨论】:

您的行 response.append(&amp;buf); 无法为我编译。 while(buf != '\r' &amp;&amp; n &gt; 0); 也没有。是这些错别字还是我遗漏了什么? 您建议发送int n_written = write( USB, cmd, sizeof(cmd) -1) 的代码实际上不适用于大缓冲区。在这种情况下,您需要一个类似于上面显示的循环,但使用n_written = write( USB, cmd + spot, command_length - spot)。此外,您必须使用长度来终止循环,而不是单独检查每个字符。 cfmakeraw() 已经设置了一堆属性。你不需要这些行:tty.c_cflag &amp;= ~CSIZE; tty.c_cflag |= CS8; tty.c_cflag &amp;= ~PARENB; 为什么(speed_t)cfsetospeed (&amp;tty, (speed_t)B9600); 中的演员阵容?在最坏的情况下,它会发出有用的警告,即使用的常量不适合speed_t。充其量是没有必要的。【参考方案2】:

一些接收者期望 EOL 序列,通常是两个字符 \r\n,因此请尝试在您的代码中替换该行

unsigned char cmd[] = 'I', 'N', 'I', 'T', ' ', '\r', '\0';

unsigned char cmd[] = "INIT\r\n";

顺便说一句,上述方式可能更有效。无需引用每个字符。

【讨论】:

另外,一些 USB 驱动器具有快速写入缓存,这意味着它在实际写入之前位于缓冲区中,对吗? 感谢您的提示,但\r\n 不是我的问题。我已经尝试过了,并没有真正改变。其他想法? @Lunatic999 我刚刚再次阅读了您的代码,int n_written = write( USB, cmd, sizeof(cmd) -1 ); 这行让我很困扰。您愿意使用 int n_written = write(USB, cmd, strlen(cmd)); 进行测试吗?这就是我在我工作的程序中所拥有的,与您的设置非常相似,通过 ttyUSB 进行通信。【参考方案3】:

1) 我会在初始化之后添加 /n。即 write(USB, "init\n", 5);

2) 仔细检查串口配置。赔率是那里的东西不正确。不使用^Q/^S或硬件流控不代表对方不期待。

3) 最有可能:write()之后添加一个"usleep(100000);。文件描述符设置为不阻塞或等待,对吗?在调用 read 之前需要多长时间才能得到响应?(它必须由内核通过系统硬件中断接收和缓冲,然后才能调用read() 它。)你有没有考虑使用 select() 来等待某些东西 read()?也许有超时? p>

编辑添加:

您需要 DTR/RTS 线路吗?告诉对方发送计算机数据的硬件流控制?例如

int tmp, serialLines;

cout << "Dropping Reading DTR and RTS\n";
ioctl ( readFd, TIOCMGET, & serialLines );
serialLines &= ~TIOCM_DTR;
serialLines &= ~TIOCM_RTS;
ioctl ( readFd, TIOCMSET, & serialLines );
usleep(100000);
ioctl ( readFd, TIOCMGET, & tmp );
cout << "Reading DTR status: " << (tmp & TIOCM_DTR) << endl;
sleep (2);

cout << "Setting Reading DTR and RTS\n";
serialLines |= TIOCM_DTR;
serialLines |= TIOCM_RTS;
ioctl ( readFd, TIOCMSET, & serialLines );
ioctl ( readFd, TIOCMGET, & tmp );
cout << "Reading DTR status: " << (tmp & TIOCM_DTR) << endl;

【讨论】:

感谢您的回答!我已经尝试了您的建议,但没有任何改变。我已经用更多信息编辑了我的问题......还有其他想法吗?再次感谢您 usleep() 仅用于调试/测试。一旦它开始工作,你可以切换到 select() 来等待响应的必要时间。 你是怎么写7个字节的?它是“INIT \r”,它是 6 个字节......你在写结尾的 NULL 字符吗?一些系统将 NULL 字符解释为通信结束... 对不起,7 个字节是我报告的错误。我肯定写了正确的字节数(6)。 7 指的是以\r\n 结尾的尝试。我会编辑我的问题。无论如何,我已经修复了所有问题,现在它可以工作了。我将发布最终代码,以防有人需要类似的东西。非常感谢您的回答!

以上是关于在 Linux 上用 C 语言读写串口的主要内容,如果未能解决你的问题,请参考以下文章

linux系统下,文件存储与数据读写问题(C语言)。

Linux C Serial串口编程

ubuntu 读写串口权限怎么永久设置

读写串口 类QSerialPort

串口读写,select 检测有数据时就接收,防止阻塞问题

如何使用C# 进行串口的读写,请详细点