如何在 C 语言中从串口打开、读取和写入?

我对读写串行端口有点困惑。我有一个在 Linux 的 USB 设备,使用 FTDI USB 串行设备转换器驱动程序。当我插入它时,它会创建:/dev/ttyUSB1。

我认为在 C 中打开和读写很简单,我知道波特率和奇偶校验信息,但似乎没有这方面的标准?

是我漏掉了什么,还是有人能给我指明正确的方向?

503979 次浏览

我很久以前写过这个(从1985年到1992年,只做了一些调整) ,只是复制粘贴到每个项目所需的位。

您必须在从 tcgetattr获得的 tty上调用 cfmakeraw。您不能对 struct termios进行调零、配置,然后再用 tcsetattr设置 tty。如果您使用零出方法,那么您将经历无法解释的间歇性故障,特别是在 BSD 和 OS X 上。“无法解释的间歇性故障”包括在 read(3)中挂起。

#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>


int
set_interface_attribs (int fd, int speed, int parity)
{
struct termios tty;
if (tcgetattr (fd, &tty) != 0)
{
error_message ("error %d from tcgetattr", errno);
return -1;
}


cfsetospeed (&tty, speed);
cfsetispeed (&tty, speed);


tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;     // 8-bit chars
// disable IGNBRK for mismatched speed tests; otherwise receive break
// as \000 chars
tty.c_iflag &= ~IGNBRK;         // disable break processing
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_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl


tty.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
// enable reading
tty.c_cflag &= ~(PARENB | PARODD);      // shut off parity
tty.c_cflag |= parity;
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;


if (tcsetattr (fd, TCSANOW, &tty) != 0)
{
error_message ("error %d from tcsetattr", errno);
return -1;
}
return 0;
}


void
set_blocking (int fd, int should_block)
{
struct termios tty;
memset (&tty, 0, sizeof tty);
if (tcgetattr (fd, &tty) != 0)
{
error_message ("error %d from tggetattr", errno);
return;
}


tty.c_cc[VMIN]  = should_block ? 1 : 0;
tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout


if (tcsetattr (fd, TCSANOW, &tty) != 0)
error_message ("error %d setting term attributes", errno);
}




...
char *portname = "/dev/ttyUSB1"
...
int fd = open (portname, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0)
{
error_message ("error %d opening %s: %s", errno, portname, strerror (errno));
return;
}


set_interface_attribs (fd, B115200, 0);  // set speed to 115,200 bps, 8n1 (no parity)
set_blocking (fd, 0);                // set no blocking


write (fd, "hello!\n", 7);           // send 7 character greeting


usleep ((7 + 25) * 100);             // sleep enough to transmit the 7 plus
// receive 25:  approx 100 uS per char transmit
char buf [100];
int n = read (fd, buf, sizeof buf);  // read up to 100 characters if ready to read

速度值为 B115200B230400B9600B19200B38400B57600B1200B2400B4800等。奇偶校验的值是 0(意味着没有奇偶校验)、 B2304000(启用奇偶校验并使用奇数)、 B2304001(启用奇偶校验并使用偶数)、 B2304002(标记奇偶校验)和 B2304003(空间奇偶校验)。

“阻塞”设置端口上的 read()是否等待指定数量的字符到达。设置 不要阻挡意味着 read()返回多少字符可用,而不需要等待更多字符,直到达到缓冲区限制。


附录:

CMSPAR仅用于选择标记和空间奇偶校验,这是不常见的。对于大多数应用程序,可以省略它。我的头文件 /usr/include/bits/termios.h只有在定义了预处理器符号 __USE_MISC时才启用 CMSPAR的定义。该定义(在 features.h中)使用

#if defined _BSD_SOURCE || defined _SVID_SOURCE
#define __USE_MISC     1
#endif

<features.h>的介绍性评论说:

/* These are defined by the user (or the compiler)
to specify the desired environment:


...
_BSD_SOURCE          ISO C, POSIX, and 4.3BSD things.
_SVID_SOURCE         ISO C, POSIX, and SVID things.
...
*/

对于符合 POSIX 标准的演示代码,如 < a href = “ https://www.gnu.org/software/libc/Manual/html _ node/Seting-Modes.html”rel = “ noReferrer”> 设置终端模式正确 以及 POSIX 操作系统的串行编程指南,提供以下服务。
这段代码应该在 x86以及 ARM (甚至 CRIS)处理器上正确地使用 Linux 执行。
它本质上来自于另一个答案,但不准确和误导性的评论已经得到纠正。

这个演示程序打开和初始化一个串行终端在115200波特的非规范模式,是便携性尽可能。
程序将一个硬编码的文本字符串传输到另一个终端,并在执行输出时延迟。
然后程序进入一个无限循环来接收和显示来自串行终端的数据。
默认情况下,接收到的数据显示为十六进制字节值。

为了使程序将接收到的数据视为 ASCII 码,使用符号 DISPLAY _ STRING 编译程序,例如。

 cc -DDISPLAY_STRING demo.c

如果接收到的数据是 ASCII 文本(而不是二进制数据) ,并且希望将其读取为以换行符结束的行,则请参阅 这个答案了解示例程序。


#define TERMINAL    "/dev/ttyUSB0"


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


int set_interface_attribs(int fd, int speed)
{
struct termios tty;


if (tcgetattr(fd, &tty) < 0) {
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}


cfsetospeed(&tty, (speed_t)speed);
cfsetispeed(&tty, (speed_t)speed);


tty.c_cflag |= (CLOCAL | CREAD);    /* ignore modem controls */
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;         /* 8-bit characters */
tty.c_cflag &= ~PARENB;     /* no parity bit */
tty.c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
tty.c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */


/* setup for non-canonical mode */
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tty.c_oflag &= ~OPOST;


/* fetch bytes as they become available */
tty.c_cc[VMIN] = 1;
tty.c_cc[VTIME] = 1;


if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
return 0;
}


void set_mincount(int fd, int mcount)
{
struct termios tty;


if (tcgetattr(fd, &tty) < 0) {
printf("Error tcgetattr: %s\n", strerror(errno));
return;
}


tty.c_cc[VMIN] = mcount ? 1 : 0;
tty.c_cc[VTIME] = 5;        /* half second timer */


if (tcsetattr(fd, TCSANOW, &tty) < 0)
printf("Error tcsetattr: %s\n", strerror(errno));
}




int main()
{
char *portname = TERMINAL;
int fd;
int wlen;
char *xstr = "Hello!\n";
int xlen = strlen(xstr);


fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0) {
printf("Error opening %s: %s\n", portname, strerror(errno));
return -1;
}
/*baudrate 115200, 8 bits, no parity, 1 stop bit */
set_interface_attribs(fd, B115200);
//set_mincount(fd, 0);                /* set to pure timed read */


/* simple output */
wlen = write(fd, xstr, xlen);
if (wlen != xlen) {
printf("Error from write: %d, %d\n", wlen, errno);
}
tcdrain(fd);    /* delay for output */




/* simple noncanonical input */
do {
unsigned char buf[80];
int rdlen;


rdlen = read(fd, buf, sizeof(buf) - 1);
if (rdlen > 0) {
#ifdef DISPLAY_STRING
buf[rdlen] = 0;
printf("Read %d: \"%s\"\n", rdlen, buf);
#else /* display hex */
unsigned char   *p;
printf("Read %d:", rdlen);
for (p = buf; rdlen-- > 0; p++)
printf(" 0x%x", *p);
printf("\n");
#endif
} else if (rdlen < 0) {
printf("Error from read: %d: %s\n", rdlen, strerror(errno));
} else {  /* rdlen == 0 */
printf("Timeout from read\n");
}
/* repeat read to get full message */
} while (1);
}

有关提供接收数据缓冲但允许逐字节处理输入的高效程序的示例,请参见 这个答案