终端连接控制(stty的编写)

终端连接控制(stty的编写)

一、背景

文件与目录在之前已经学习过了。文件中包含着数据,这些数据可以被读出、写入,也可以用以操作。但文件不仅仅是计算机唯一的数据来源,计算机的数据还可以来自于许多的外部设备,比如扫描仪、照相机、鼠标等输入设备,还有扬声器、打印机等输出设备。总之数据来源多种多样,并不仅局限于文件。那么这些外部设备的连接是怎么样的呢,其与进程协同工作时的方式(属性)如何来修改呢。这就是本文所要探究的。

二、外部设备与磁盘文件的联系

文件我们都已经很熟悉了,文件通过目录中的文件名来定位,文件名有一个i-节点与之对应。而i-节点中有文件的属性且包含着指针列表(此指针列表指向文件的数据内容)。而实际上,外部设备在unix系统中也是以文件的形式存储的。我们可以从文件中读数据也可以写数据。同样,我们也可以向设备文件写数据或从中读出数据。

外部设备的文件一般存储于/dev中。

 

这里显示的是我的一些设备文件。但是并不是说所有的设备文件必须要存储于/dev下,并不是这样的,只是一种约定成俗的习惯,当然也可以在别的目录下创建设备文件。

三、终端文件

要想看自己的终端文件名,只需输入tty即可。我的(root)终端文件是这样的。

 

而在另一个终端的用户的终端文件是这样的。

 

如此,如我前面所述,如果我可以使用root用户往/dev/pts/1文件中写入数据,那么应该就可以在用户pf的屏幕上显示出我所写入的数据。这样,我写了一段代码用于此功能,实际上这个即是命令write的功能。

如下所示:

  

这就很清楚的演示了设备文件(终端文件)的操作方法与磁盘文件十分类似,许多的系统调用也都是通用的。

四、外部设备与磁盘文件的不同

第二节讲了它们是十分相似的,都是以文件形式来进行操作。并且系统调用往往也是通用的。但它们却不可能是完全相同的。不同之处主要体现在连接属性上。对于文件来说,它们与进程进行数据的传输是需要缓冲的,缓冲用于文件数据的传输上好处很多,最突出的是缓冲可以容错,而且缓冲是以块为单位传输的,比起字节为单位传输,效率要高很多。但这是文件的连接属性之一,可以开启也可以关闭。但对于终端来说(以终端为例),当然是不需要缓冲,数据需要尽快的从进程传输到终端上来。并且终端也有其许多的连接属性是文件连接属性所没有的,比如回显、奇偶位、暂停位。

五、终端连接的属性

文件连接的属性这里就不啰嗦了,我主要想讨论一下终端连接的属性。

命令stty用于显示或修改终端的连接属性。

显示全部的终端连接属性用-a参数。如下所示:

 

这里有三种类型的属性,一种是开关型,一种是数字型、一种是字符控制型。类型是这么分的,在修改对应类型的属性时也是不同的。具体如何修改?对于开关型的属性,只需输入stty  (-)属性名,括号中的减号可加可不加,若不加,则是打开此属性开关,若加,则是关闭此属性开关。对于数字型与字符控制型,只需输入stty 属性名 修改值,当然,需要修改什么属性就输入什么属性名,而修改值根据自己的需求来定。

六、stty的编程

stty的编程并不复杂,主要会使用两个系统调用:tcgetattrtcsetattr。前一个用来将设备文件的信息传出来,后一个用来将修改过后的信息传回去。并使用掩码来进行测试、置位与清除位。

这里我先编写了一个简单的echo的开关文件。结果如下所示:

 

代码如下:

#include<stdio.h>

#include<termios.h>

#include<stdlib.h>

int main()

{

  struct termios info;

  if(tcgetattr(0,&info)==-1){

    perror("tcgetattr");

    exit(1);

    }

  else{

    if(info.c_lflag&ECHO)

      info.c_lflag&=~ECHO;

    else

      info.c_lflag|=ECHO;

    if(tcsetattr(0,TCSANOW,&info)==-1)

      perror("tcsetattr");

    }

}

  

自己编写的stty结果如下:

 

 

我这里还是拿echo来做测试的,因为结果很明显。


实现stty的代码如下:

#include<stdio.h>

#include<termios.h>

#include<stdlib.h>

#include<string.h>

 

struct termios execute_1(struct termios buff,char *av);

struct termios execute_2(struct termios buff,char *av_1,char *av_2);

 

int main(int argc,char *argv[])

{

  struct termios info;

  struct termios buf;

  if(argc!=2 && argc!=3){

    printf("Usage error\n");

    exit(1);

    }

  if(tcgetattr(0,&info)==-1){

    perror("tcgetattr");

    exit(1);

    }

  if(argc==2){

    buf=execute_1(info,argv[1]);

    tcsetattr(0,TCSANOW,&buf);

    }

  if(argc==3){

    buf=execute_2(info,argv[1],argv[2]);

    tcsetattr(0,TCSANOW,&buf);

    }

}

 

struct termios execute_1(struct termios buff,char *av)

{

  if(strcmp(av,"ignbrk")==0)

    buff.c_iflag|=IGNBRK;

  if(strcmp(av,"-ignbrk")==0)

    buff.c_iflag&=~IGNBRK;

  if(strcmp(av,"brkint")==0)

    buff.c_iflag|=BRKINT;

  if(strcmp(av,"-brkint")==0)

    buff.c_iflag&=~BRKINT;

  if(strcmp(av,"ignpar")==0)

    buff.c_iflag|=IGNPAR;

  if(strcmp(av,"-ignpar")==0)

    buff.c_iflag&=~IGNPAR;

  if(strcmp(av,"parmrk")==0)

    buff.c_iflag|=PARMRK;

  if(strcmp(av,"-parmrk")==0)

    buff.c_iflag&=~PARMRK;

  if(strcmp(av,"inpck")==0)

    buff.c_iflag|=INPCK;

  if(strcmp(av,"-inpck")==0)

    buff.c_iflag&=~INPCK;

  if(strcmp(av,"istrip")==0)

    buff.c_iflag|=ISTRIP;

  if(strcmp(av,"-istrip")==0)

    buff.c_iflag&=~ISTRIP;

  if(strcmp(av,"inlcr")==0)

    buff.c_iflag|=INLCR;

  if(strcmp(av,"-inlcr")==0)

    buff.c_iflag&=~INLCR;

  if(strcmp(av,"igncr")==0)

    buff.c_iflag|=IGNCR;

  if(strcmp(av,"-igncr")==0)

    buff.c_iflag&=~IGNCR;

  if(strcmp(av,"icrnl")==0)

    buff.c_iflag|=ICRNL;

  if(strcmp(av,"-icrnl")==0)

    buff.c_iflag&=~ICRNL;

  if(strcmp(av,"iuclc")==0)

    buff.c_iflag|=IUCLC;

  if(strcmp(av,"-iuclc")==0)

    buff.c_iflag&=~IUCLC;

  if(strcmp(av,"ixon")==0)

    buff.c_iflag|=IXON;

  if(strcmp(av,"-ixon")==0)

    buff.c_iflag&=~IXON;

  if(strcmp(av,"ixany")==0)

    buff.c_iflag|=IXANY;

  if(strcmp(av,"-ixany")==0)

    buff.c_iflag&=~IXANY;

  if(strcmp(av,"ixoff")==0)

    buff.c_iflag|=IXOFF;

  if(strcmp(av,"-ixoff")==0)

    buff.c_iflag&=~IXOFF;

  if(strcmp(av,"imaxbel")==0)

    buff.c_iflag|=IMAXBEL;

  if(strcmp(av,"-imaxbel")==0)

    buff.c_iflag&=~IMAXBEL;

  if(strcmp(av,"opost")==0)

    buff.c_oflag|=OPOST;

  if(strcmp(av,"-opost")==0)

    buff.c_oflag&=~OPOST;

  if(strcmp(av,"onlcr")==0)

    buff.c_oflag|=ONLCR;

  if(strcmp(av,"-onlcr")==0)

    buff.c_oflag&=~ONLCR;

  if(strcmp(av,"olcuc")==0)

    buff.c_oflag|=OLCUC;

  if(strcmp(av,"-olcuc")==0)

    buff.c_oflag&=~OLCUC;

  if(strcmp(av,"ocrnl")==0)

    buff.c_oflag|=OCRNL;

  if(strcmp(av,"-ocrnl")==0)

    buff.c_oflag&=~OCRNL;

  if(strcmp(av,"onlret")==0)

    buff.c_oflag|=ONLRET;

  if(strcmp(av,"-onlret")==0)

    buff.c_oflag&=~ONLRET;

  if(strcmp(av,"ofill")==0)

    buff.c_oflag|=OFILL;

  if(strcmp(av,"-ofill")==0)

    buff.c_oflag&=~OFILL;

  if(strcmp(av,"ofdel")==0)

    buff.c_oflag|=OFDEL;

  if(strcmp(av,"-ofdel")==0)

    buff.c_oflag&=~OFDEL;

  if(strcmp(av,"nldly")==0)

    buff.c_oflag|=NLDLY;

  if(strcmp(av,"-nldly")==0)

    buff.c_oflag&=~NLDLY;

  if(strcmp(av,"crdly")==0)

    buff.c_oflag|=CRDLY;

  if(strcmp(av,"-crdly")==0)

    buff.c_oflag&=~CRDLY;

  if(strcmp(av,"tabdly")==0)

    buff.c_oflag|=TABDLY;

  if(strcmp(av,"-tabdly")==0)

    buff.c_oflag&=~TABDLY;

  if(strcmp(av,"bsdly")==0)

    buff.c_oflag|=BSDLY;

  if(strcmp(av,"-bsdly")==0)

    buff.c_oflag&=~BSDLY;

  if(strcmp(av,"ffdly")==0)

    buff.c_oflag|=FFDLY;

  if(strcmp(av,"-ffdly")==0)

    buff.c_oflag&=~FFDLY;

  if(strcmp(av,"vtdly")==0)

    buff.c_oflag|=VTDLY;

  if(strcmp(av,"-vtdly")==0)

    buff.c_oflag&=~VTDLY;

  if(strcmp(av,"csize")==0)

    buff.c_cflag|=CSIZE;

  if(strcmp(av,"-csize")==0)

    buff.c_cflag&=~CSIZE;

  if(strcmp(av,"cstopb")==0)

    buff.c_cflag|=CSTOPB;

  if(strcmp(av,"-cstopb")==0)

    buff.c_cflag&=~CSTOPB;

  if(strcmp(av,"cread")==0)

    buff.c_cflag|=CREAD;

  if(strcmp(av,"-cread")==0)

    buff.c_cflag&=~CREAD;

  if(strcmp(av,"parenb")==0)

    buff.c_cflag|=PARENB;

  if(strcmp(av,"-parenb")==0)

    buff.c_cflag&=~PARENB;

  if(strcmp(av,"parodd")==0)

    buff.c_cflag|=PARODD;

  if(strcmp(av,"-parodd")==0)

    buff.c_cflag&=~PARODD;

  if(strcmp(av,"hupcl")==0)

    buff.c_cflag|=HUPCL;

  if(strcmp(av,"-hupcl")==0)

    buff.c_cflag&=~HUPCL;

  if(strcmp(av,"clocal")==0)

    buff.c_cflag|=CLOCAL;

  if(strcmp(av,"-clocal")==0)

    buff.c_cflag&=~CLOCAL;

  if(strcmp(av,"crtscts")==0)

    buff.c_cflag|=CRTSCTS;

  if(strcmp(av,"-crtscts")==0)

    buff.c_cflag&=~CRTSCTS;

  if(strcmp(av,"isig")==0)

    buff.c_lflag|=ISIG;

  if(strcmp(av,"-isig")==0)

    buff.c_lflag&=~ISIG;

  if(strcmp(av,"icanon")==0)

    buff.c_lflag|=ICANON;

  if(strcmp(av,"-icanon")==0)

    buff.c_lflag&=~ICANON;

  if(strcmp(av,"echoe")==0)

    buff.c_lflag|=ECHOE;

  if(strcmp(av,"-echoe")==0)

    buff.c_lflag&=~ECHOE;

  if(strcmp(av,"echok")==0)

    buff.c_lflag|=ECHOK;

  if(strcmp(av,"-echok")==0)

    buff.c_lflag&=~ECHOK;

  if(strcmp(av,"echoke")==0)

    buff.c_lflag|=ECHOKE;

  if(strcmp(av,"-echoke")==0)

    buff.c_lflag&=~ECHOKE;

  if(strcmp(av,"echoctl")==0)

    buff.c_lflag|=ECHOCTL;

  if(strcmp(av,"-echoctl")==0)

    buff.c_lflag&=~ECHOCTL;

  if(strcmp(av,"echo")==0)

    buff.c_lflag|=ECHO;

  if(strcmp(av,"-echo")==0)

    buff.c_lflag&=~ECHO;

  if(strcmp(av,"echonl")==0)

    buff.c_lflag|=ECHONL;

  if(strcmp(av,"-echonl")==0)

    buff.c_lflag&=~ECHONL;

  if(strcmp(av,"iexten")==0)

    buff.c_lflag|=IEXTEN;

  if(strcmp(av,"-iexten")==0)

    buff.c_lflag&=~IEXTEN;

  if(strcmp(av,"noflsh")==0)

    buff.c_lflag|=NOFLSH;

  if(strcmp(av,"-noflsh")==0)

    buff.c_lflag&=~NOFLSH;

  if(strcmp(av,"tostop")==0)

    buff.c_lflag|=TOSTOP;

  if(strcmp(av,"-tostop")==0)

    buff.c_lflag&=~TOSTOP;

  if(strcmp(av,"pendin")==0)

    buff.c_lflag|=PENDIN;

  if(strcmp(av,"-pendin")==0)

    buff.c_lflag&=~PENDIN;

  if(strcmp(av,"flusho")==0)

    buff.c_lflag|=FLUSHO;

  if(strcmp(av,"-flusho")==0)

    buff.c_lflag&=~FLUSHO;

  return buff;

}

 

struct termios execute_2(struct termios buff,char *av_1,char *av_2)

{

  if(strcmp(av_1,"intr")==0)

    buff.c_cc[VINTR]=*av_2;

  if(strcmp(av_1,"quit")==0)

    buff.c_cc[VQUIT]=*av_2;

  if(strcmp(av_1,"erase")==0)

    buff.c_cc[VERASE]=*av_2;

  if(strcmp(av_1,"kill")==0)

    buff.c_cc[VKILL]=*av_2;

  if(strcmp(av_1,"eof")==0)

    buff.c_cc[VEOF]=*av_2;

  if(strcmp(av_1,"min")==0)

    buff.c_cc[VMIN]=*av_2;

  if(strcmp(av_1,"eol")==0)

    buff.c_cc[VEOL]=*av_2;

  return buff;

}


七、总结

    这是我第一次从系统的角度来观察设备文件,对于设备文件的学习当然是对比更为熟悉的磁盘文件。从异、同两个角度来观察,相同之处在于两者都是以文件的形式在计算机进行存储,表面来看十分相似,且两者的系统调用也基本通用。不同之处在于连接属性决然不一样。这是理所当然的事情,具体问题具体分析,各个不同的设备文件的连接属性也都不同,对终端来说,属性有回显、奇偶位、暂停位等等,对CD刻录机来说,属性有刻录速度、颜色深度等等。我这里只大概的分析了一下终端的连接属性。往后会更多的分析其它的设备文件连接属性。





 

<think>我们使用C++在Linux环境下(虚拟机中)实现串口通信的上位机程序。我们将使用以下头文件: #include <fcntl.h> // 文件控制定义 #include <unistd.h> // UNIX标准函数 #include <termios.h> // POSIX终端控制定义 步骤: 1. 打开串口设备文件(例如:/dev/ttyS0 或 /dev/ttyUSB0) 2. 配置串口参数(波特率、数据位、停止位、校验位等) 3. 读写数据 4. 关闭串口 注意:在虚拟机中,需要确保串口设备已经正确映射。例如,在VirtualBox中,可以设置串口端口,并连接到主机上的物理串口或者虚拟串口(如命名管道)。 下面是一个完整的示例程序,实现了串口的打开、配置、发送和接收。 ```cpp #include <fcntl.h> #include <unistd.h> #include <termios.h> #include <cstring> #include <iostream> #include <string> // 打开串口 int open_serial_port(const char *port_name) { // 以读写方式打开,不使串口成为控制终端(O_NOCTTY),非阻塞模式(O_NDELAY) int fd = open(port_name, O_RDWR | O_NOCTTY | O_NDELAY); if (fd < 0) { std::cerr << "Error opening serial port: " << port_name << " - " << strerror(errno) << std::endl; return -1; } // 恢复阻塞模式,即等待数据 fcntl(fd, F_SETFL, 0); return fd; } // 配置串口 bool configure_serial_port(int fd, int baud_rate) { struct termios options; memset(&options, 0, sizeof(options)); // 获取当前属性 if (tcgetattr(fd, &options) != 0) { std::cerr << "Error getting serial port attributes: " << strerror(errno) << std::endl; return false; } // 设置输入输出波特率 cfsetispeed(&options, baud_rate); cfsetospeed(&options, baud_rate); // 启用接收和忽略调制解调器状态线 options.c_cflag |= (CLOCAL | CREAD); // 设置数据位:8位 options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; // 设置校验位:无校验 options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; // 1位停止位 // 设置原始输入模式(禁用规范模式,不回显,不处理信号) options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 关闭软件流控 options.c_iflag &= ~(IXON | IXOFF | IXANY); options.c_iflag &= ~(INLCR | ICRNL); // 禁止将回车转换为换行 // 原始输出模式 options.c_oflag &= ~OPOST; // 设置超时:100毫秒(VTIME单位为0.1秒,所以设置为1表示0.1秒,即100毫秒) options.c_cc[VTIME] = 1; // 等待数据时间(单位:0.1秒) options.c_cc[VMIN] = 0; // 最小读取字符数(0表示非阻塞) // 刷新输入输出队列并立即应用配置 tcflush(fd, TCIOFLUSH); if (tcsetattr(fd, TCSANOW, &options) != 0) { std::cerr << "Error setting serial port attributes: " << strerror(errno) << std::endl; return false; } return true; } // 发送数据 bool send_data(int fd, const char *data, size_t length) { ssize_t n = write(fd, data, length); if (n < 0) { std::cerr << "Error writing to serial port: " << strerror(errno) << std::endl; return false; } else if (n != static_cast<ssize_t>(length)) { std::cerr << "Warning: not all data written to serial port" << std::endl; return false; } return true; } // 接收数据(非阻塞,返回读取到的字节数,0表示没有数据,-1表示错误) int receive_data(int fd, char *buffer, size_t buffer_size) { ssize_t n = read(fd, buffer, buffer_size - 1); // 留一个位置给字符串结束符 if (n < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { // 非阻塞模式下,没有数据可读 return 0; } std::cerr << "Error reading from serial port: " << strerror(errno) << std::endl; return -1; } // 将读取的数据以字符串形式结束(如果需要,调用者可以自行处理二进制数据) buffer[n] = '\0'; return n; } int main() { // 串口设备名,根据实际情况修改 const char *port_name = "/dev/ttyS0"; int baud_rate = B115200; // 波特率115200 // 打开串口 int fd = open_serial_port(port_name); if (fd < 0) { return 1; } // 配置串口 if (!configure_serial_port(fd, baud_rate)) { close(fd); return 1; } // 示例:发送字符串 std::string message = "Hello, Serial Port!\n"; if (!send_data(fd, message.c_str(), message.length())) { std::cerr << "Failed to send data." << std::endl; } // 接收数据(循环接收一段时间) char buffer[256]; std::cout << "Waiting for data (for 10 seconds)..." << std::endl; for (int i = 0; i < 10; i++) { int n = receive_data(fd, buffer, sizeof(buffer)); if (n > 0) { std::cout << "Received: " << buffer << std::endl; } else if (n < 0) { break; } sleep(1); // 等待1秒 } // 关闭串口 close(fd); return 0; } ``` ### 编译和运行 1. 保存为`serial_example.cpp` 2. 使用g++编译: ```bash g++ serial_example.cpp -o serial_example ``` 3. 运行程序(需要串口读写权限): ```bash sudo ./serial_example ``` 或者将当前用户加入`dialout`组(可能需要注销重新登录): ```bash sudo usermod -aG dialout $USER ``` ### 虚拟机配置注意事项 在虚拟机(如VirtualBox)中设置串口: 1. 关闭虚拟机 2. 在虚拟机设置中添加串口设备 3. 选择端口模式(例如:主机管道、物理串口等)和端口路径 4. 启动虚拟机,确认串口设备节点(如`/dev/ttyS0`)存在 ### 常见问题 - **权限问题**:确保运行程序的用户有权限访问串口设备(通常需要`dialout`组)。 - **设备不存在**:检查虚拟机串口设置和Linux中的设备节点(`/dev/ttyS0`,`/dev/ttyUSB0`等)。 - **波特率不匹配**:确保两端(上位机和下位机)使用相同的波特率、数据位、停止位和校验位。 ### 相关概念解释 1. **`termios`结构**:用于配置串口通信参数,包括输入输出波特率、数据位、停止位、校验位等。 2. **`c_cflag`**:控制模式标志,用于设置波特率、数据位、停止位、校验位等。 3. **`c_lflag`**:本地模式标志,用于控制终端输入处理(如回显、规范模式等)。 4. **`c_iflag`**:输入模式标志,控制输入处理(如软件流控、回车换行转换等)。 5. **`c_oflag`**:输出模式标志,控制输出处理(如输出映射、延迟等)。 6. **`c_cc`数组**:控制字符和超时设置,如`VMIN`(最小读取字符数)和`VTIME`(超时时间)。 ### 相关问题 1. 如何在Linux中检查串口设备是否存在? 2. 如何设置串口的硬件流控(RTS/CTS)? 3. 串口通信中如何处理二进制数据(非文本)? 4. 如何实现多线程串口通信(一个线程发送,一个线程接收)? [^1]: 参考示例中的Java串口调用接口,本C++实现直接使用系统调用。</think>### 在虚拟机中使用C++实现串口通信(上位机程序) 以下是完整的C++实现方案,使用`fcntl.h`、`unistd.h`和`termios.h`头文件在Linux虚拟机中实现串口通信: ```cpp #include <fcntl.h> #include <unistd.h> #include <termios.h> #include <cstring> #include <iostream> #include <string> class SerialPort { public: SerialPort(const char* port, int baud_rate = B115200) : fd_(-1), port_(port) { open_port(baud_rate); } ~SerialPort() { if (is_open()) close(fd_); } bool is_open() const { return fd_ >= 0; } bool send(const std::string& data) { if (!is_open()) return false; ssize_t n = write(fd_, data.c_str(), data.size()); if (n < 0) { perror("Write failed"); return false; } return n == static_cast<ssize_t>(data.size()); } std::string receive() { if (!is_open()) return ""; char buffer[256]; ssize_t n = read(fd_, buffer, sizeof(buffer) - 1); if (n > 0) { buffer[n] = '\0'; return std::string(buffer); } else if (n < 0) { perror("Read error"); } return ""; } private: void open_port(int baud_rate) { // 打开串口设备 (非阻塞模式) fd_ = open(port_, O_RDWR | O_NOCTTY | O_NDELAY); if (fd_ < 0) { perror("Error opening serial port"); return; } // 恢复阻塞模式 fcntl(fd_, F_SETFL, 0); // 配置串口参数 struct termios options; tcgetattr(fd_, &options); // 设置波特率 cfsetispeed(&options, baud_rate); cfsetospeed(&options, baud_rate); // 8位数据位,无校验,1位停止位 options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; // 启用接收器 options.c_cflag |= (CLOCAL | CREAD); // 原始输入模式(禁用规范处理) options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 禁用软件流控 options.c_iflag &= ~(IXON | IXOFF | IXANY); // 原始输出模式 options.c_oflag &= ~OPOST; // 设置超时:100ms等待,最小读取0字节 options.c_cc[VTIME] = 1; // 0.1秒单位 options.c_cc[VMIN] = 0; // 应用配置 tcsetattr(fd_, TCSANOW, &options); tcflush(fd_, TCIOFLUSH); // 清空缓冲区 } int fd_; const char* port_; }; int main() { // 在虚拟机中通常使用 /dev/ttyS0 或 /dev/ttyUSB0 SerialPort serial("/dev/ttyS0", B115200); if (!serial.is_open()) { std::cerr << "Failed to open serial port" << std::endl; return 1; } // 发送数据示例 serial.send("Hello from VM!\n"); // 接收数据示例 while (true) { std::string data = serial.receive(); if (!data.empty()) { std::cout << "Received: " << data; } usleep(100000); // 100ms延迟 } return 0; } ``` ### 关键配置说明 1. **串口设备路径**: - `/dev/ttyS0`:虚拟机常用串口设备 - `/dev/ttyUSB0`:当使用USB转串口适配器时 2. **波特率常量**: ```cpp B9600, B19200, B38400, B57600, B115200 ``` 3. **termios结构重要参数**: | 参数 | 作用 | 常用值 | |---------------|-------------------------------|---------------------| | `c_cflag` | 控制模式标志 | `CS8 \| CLOCAL \| CREAD` | | `c_lflag` | 本地模式标志 | `0` (原始模式) | | `c_iflag` | 输入模式标志 | `0` (禁用特殊处理) | | `c_cc[VTIME]` | 字符间超时 (0.1秒单位) | `1` (100ms) | | `c_cc[VMIN]` | 最小读取字符数 | `0` (非阻塞) | ### 编译与运行步骤 1. **编译程序**: ```bash g++ -o serial_app serial.cpp ``` 2. **设置权限**(首次需要): ```bash sudo usermod -aG dialout $USER # 添加当前用户到串口组 newgrp dialout # 立即生效组更改 ``` 3. **运行程序**: ```bash ./serial_app ``` ### 虚拟机配置要点 1. **VirtualBox设置**: - 关闭虚拟机 → 设置 → 串口 → 启用端口 - 端口模式:**主机管道**(推荐)或**主机设备** - 端口路径:`/tmp/vbox`(Linux/Mac)或 `\\.\pipe\vbox`(Windows) 2. **VMware设置**: - 虚拟机设置 → 添加 → 串行端口 - 选择**使用命名管道**,路径同上 3. **连接验证**: ```bash # 在另一个终端测试 echo "Test" > /dev/ttyS0 cat < /dev/ttyS0 ``` ### 常见问题解决 1. **权限错误**: ```bash sudo chmod a+rw /dev/ttyS0 # 临时方案 ``` 2. **无数据接收**: - 检查物理连接(TX-RX交叉) - 验证波特率是否匹配:`stty -F /dev/ttyS0 115200` 3. **数据乱码**: ```cpp // 确保配置一致 options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; // 8位数据位 ``` 4. **资源释放**: ```cpp ~SerialPort() { if (fd_ != -1) { tcflush(fd_, TCIOFLUSH); close(fd_); } } ``` ### 性能优化建议 1. **使用select/poll多路复用**: ```cpp fd_set readfds; FD_ZERO(&readfds); FD_SET(fd_, &readfds); struct timeval timeout = {0, 100000}; // 100ms select(fd_ + 1, &readfds, NULL, NULL, &timeout); ``` 2. **二进制数据传输**: ```cpp bool send_binary(const uint8_t* data, size_t len) { return write(fd_, data, len) == static_cast<ssize_t>(len); } ``` 3. **自定义超时设置**: ```cpp void set_timeout(int deciseconds) { termios options; tcgetattr(fd_, &options); options.c_cc[VTIME] = deciseconds; tcsetattr(fd_, TCSANOW, &options); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值