串口编程—(2)编程步骤及参数设置

本文详细介绍了在LINUX环境下进行串口编程的步骤,包括打开串口、设置串口参数、读写操作和关闭串口。主要内容涉及串口文件位于/dev下的路径,如/dev/ttyS0和/dev/ttyS1,以及如何使用open函数打开串口。重点讲解了struct termios结构体的设置,如波特率、校验位、停止位等,并提到了c_cflag、c_lflag和c_oflag等重要参数。最后提及了tcsetattr函数在设置终端参数时的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LINUX 下

串口操作需要的头文件

  1. #include     <stdio.h>      /*标准输入输出定义*/  
  2. #include     <stdlib.h>     /*标准函数库定义*/  
  3. #include     <unistd.h>     /*Unix 标准函数定义*/  
  4. #include     <sys/types.h>    
  5. #include     <sys/stat.h>     
  6. #include     <fcntl.h>      /*文件控制定义*/  
  7. #include     <termios.h>    /*PPSIX 终端控制定义*/  
  8. #include     <errno.h>      /*错误号定义*/  

简单来说串口编程的步骤分为:

(1)、打开串口。

(2)、设置串口。

(3)、读写串口。

(4)、关闭串口。


下面来具体说一下每个步骤:

(1)、打开串口使用 open函数

 int open(const char *pathname,  int oflag, ... /* mode_t mode */);

  返回值:成功则返回 文件描述符 ,否则返回 -1
  对于 open 函数来说,第三个参数(...)仅当创建新文件时(即 使用了O_CREAT 时)才使用,用于指定文件的访问权限位(access permission bits)。pathname 是待打开/创建文件的路径名(如 C:/cpp/a.cpp);oflag 用于指定文件的打开/创建模式,这个参数可由以下常量(定义于 fcntl.h)通过逻辑或构成。
  O_RDONLY 只读模式
  O_WRONLY 只写模式
  O_RDWR 读写模式
  打开/创建文件时,至少得使用上述三个常量中的一个。以下常量是选用的:
  O_APPEND 每次写操作都写入文件的末尾
  O_CREAT 如果指定文件不存在,则创建这个文件
  O_EXCL 如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
  O_TRUNC 如果文件存在,并且以只写/读写方式打开,则清空文件全部内容(即将其长度截短为0)
  O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。
  O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O

打开串口(USB)

在 Linux 下串口文件是位于 /dev 下的

串口一 为 /dev/ttyS0(/dev/ttyS0)

串口二 为 /dev/ttyS1(/dev/ttyS1)

打开串口是通过使用标准的文件打开函数操作:

int fd;
/*以读写方式打开串口*/
fd = open( "/dev/ttyS0", O_RDWR);
if (-1 == fd)
  { 
/* 不能打开串口一*/ 
perror(" 提示错误!");
}
fd是文件描述符

    在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。POSIX标准要求每次打开文件时(含socket)必须使用当前进程中最小可用的文件描述符号码,因此,在网络通信过程中稍不注意就有可能造成串话。标准文件描述符图如下:


终端设备设置

最基本的设置包括波特率设置,效验位和停止位设置。

串口的设置主要是设置 struct termios 结构体的各成员值。

struct termio
{	unsigned short  c_iflag;	/* 输入模式标志 */	
	unsigned short  c_oflag;		/* 输出模式标志 */	
	unsigned short  c_cflag;		/* 控制模式标志*/	
	unsigned short  c_lflag;		/* local mode flags */	
	unsigned char  c_line;		    /* line discipline */	
	unsigned char  c_cc[NCC];    /* control characters */
};
其具体意义如下。
  c_iflag:输入模式标志,控制终端输入方式,具体参数如表

键 值                  说 明
IGNBRK       忽略BREAK键输入
BRKINT         如果设置了IGNBRK,BREAK键输入将被忽略
IGNPAR       忽略奇偶校验错误
PARMRK     标识奇偶校验错误
INPCK         允许输入奇偶校验
ISTRIP         去除字符的第8个比特
INLCR             将输入的NL(换行)转换成CR(回车)
IGNCR         忽略输入的回车
ICRNL         将输入的回车转化成换行(如果IGNCR未设置的情况下)
IUCLC         将输入的大写字符转换成小写字符(非POSIX)
IXON         允许输入时对XON/XOFF流进行控制
IXANY         输入任何字符将重启停止的输出
IXOFF         允许输入时对XON/XOFF流进行控制
IMAXBEL         当输入队列满的时候开始响铃



c_oflag:输出模式标志,控制终端输出方式,具体参数如表
  
键 值    说 明
OPOST    处理后输出
OLCUC    将输入的小写字符转换成大写字符(非POSIX)
ONLCR    将输入的NL(换行)转换成CR(回车)及NL(换行)
OCRNL    将输入的CR(回车)转换成NL(换行)
ONOCR    第一行不输出回车符
ONLRET    不输出回车符
OFILL         发送填充字符以延迟终端输出
OFDEL    以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符为NUL
NLDLY    换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s)
CRDLY    回车延迟,取值范围为:CR0、CR1、CR2和 CR3
TABDLY    水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3
BSDLY    空格输出延迟,可以取BS0或BS1
VTDLY    垂直制表符输出延迟,可以取VT0或VT1
FFDLY    换页延迟,可以取FF0或FF1


c_cflag:控制模式标志,指定终端硬件控制信息,具体参数如表3所示。
  表3 c_cflag参数
  
键 值                    说 明
CBAUD              波特率(4+1位)(非POSIX)
CBAUDEX         附加波特率(1位)(非POSIX)
CSIZE                   字符长度,取值范围为CS5、CS6、CS7或CS8
CSTOPB         设置两个停止位
CREAD         使用接收器
PARENB         使用奇偶校验
PARODD         对输入使用奇偶校验,对输出使用偶校验
HUPCL         关闭设备时挂起
CLOCAL         忽略调制解调器线路状态
CRTSCTS         使用RTS/CTS流控制


c_lflag:本地模式标志,控制终端编辑功能,具体参数如表4所示。
  表4 c_lflag参数
  
键 值    说 明
ISIG                   当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号
ICANON                   使用标准输入模式
XCASE              在ICANON和XCASE同时设置的情况下,终端只使用大写。
ECHO         显示输入字符
ECHOE          如果ICANON同时设置,ERASE将删除输入的字符
ECHOK          如果ICANON同时设置,KILL将删除当前行
ECHONL       如果ICANON同时设置,即使ECHO没有设置依然显示换行符
ECHOPRT    如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX)
TOSTOP    向后台输出发送SIGTTOU信号


c_cc[NCCS]:控制字符,用于保存终端驱动程序中的特殊字符,如输入结束符等。c_cc中定义了如表所示的控制字符。
  表 c_cc支持的控制字符

宏    说 明    宏    说 明
VINTR    Interrupt字符    VEOL    附加的End-of-file字符
VQUIT    Quit字符    VTIME    非规范模式读取时的超时时间
VERASE    Erase字符    VSTOP    Stop字符
VKILL    Kill字符    VSTART    Start字符
VEOF    End-of-file字符    VSUSP    Suspend字符
VMIN    非规范模式读取时的最小字符数    ​

t


csetattr函数用于设置终端的相关参数。参数fd为打开的终端文件描述符,参数optional_actions用于控制修改起作用的时间,而结构体termios_p中保存了要修改的参数。
  optional_actions可以取如下的值:
  TCSANOW:不等数据传输完毕就立即改变属性。
  TCSADRAIN:等待所有数据传输结束才改变属性。
  TCSAFLUSH:清空输入输出缓冲区才改变属性。
  错误信息:
  EBADF:非法的文件描述符。
  EINTR:tcsetattr函数调用被信号中断。
  EINVAL:参数optional_actions使用了非法值,或参数termios中使用了非法值。
  ENCTTY:非终端的文件描述符。



设置这个结构体很复杂,我这里就只说说常见的一些设置:

波特率设置

    ​
truct  termios Opt;
tcgetattr(fd, &Opt);//tcgetattr函数用于获取与终端相关的参数。参数fd为终端的文件描述符,返回的结果保存在termios结构体中,该结构体一般包括如下的成员:
cfsetispeed(&Opt,B19200);     /*设置为19200Bps*/
cfsetospeed(&Opt,B19200);
tcsetattr(fd,TCANOW,&Opt);
tcsetattr函数用于设置终端参数。函数在成功的时候返回0,失败的时候返回-1,并设置errno的值。参数fd为打开的终端文件描述符,参数optional_actions用于控制修改起作用的时间,而结构体termios_p中保存了要修改的参数。optional_actions可以取如下的值。 
 TCSANOW:不等数据传输完毕就立即改变属性。 
 TCSADRAIN:等待所有数据传输结束才改变属性。 
 TCSAFLUSH:清空输入输出缓冲区才改变属性。

设置波特率的例子函数:

/**
*@brief  设置串口通信速率
*@param  fd     类型 int  打开串口的文件句柄
*@param  speed  类型 int  串口速度
*@return  void
*/
int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,
          B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {38400,  19200,  9600,  4800,  2400,  1200,  300, 38400,  
          19200,  9600, 4800, 2400, 1200,  300, };
void set_speed(int fd, int speed){
  int   i; 
  int   status; 
  struct termios   Opt;
  tcgetattr(fd, &Opt); 
  for ( i= 0;  i < sizeof(speed_arr) / sizeof(int);  i++) { //计算数组中的数据个数
    if  (speed == name_arr[i]) {     
      tcflush(fd, TCIOFLUSH);     
/*
tcflush() 丢弃要写入引用的对象,但是尚未传输的数据,或者收到但是尚未读取的数据,取决于 queue_selector 的值:
  TCIFLUSH   刷新收到的数据但是不读
  TCOFLUSH   刷新写入的数据但是不传送
  TCIOFLUSH  同时刷新收到的数据但是不读,并且刷新写入的数据但是不传送
    通俗地说就是将输出缓冲器清空,把输入缓冲区清空。缓冲区里的数据都废弃。
      cfsetispeed(&Opt, speed_arr[i]);  
      cfsetospeed(&Opt, speed_arr[i]);   
      status = tcsetattr(fd1, TCSANOW, &Opt);  
      if  (status != 0) 
	{        
        	perror("tcsetattr fd1");  
        	return;     
      	}    
      tcflush(fd,TCIOFLUSH);   


效验位和停止位的设置:

设置效验的函数:

/**
*@brief   设置串口数据位,停止位和效验位
*@param  fd     类型  int  打开的串口文件句柄
*@param  databits 类型  int 数据位   取值 为 7 或者8
*@param  stopbits 类型  int 停止位   取值为 1 或者2
*@param  parity  类型  int  效验类型 取值为N,E,O,,S
*/
int set_Parity(int fd,int databits,int stopbits,int parity)
{ 
	struct termios options; 
	if  ( tcgetattr( fd,&options)  !=  0) { 
		perror("SetupSerial 1");     
		return(FALSE);  
	}
	options.c_cflag &= ~CSIZE; 
	switch (databits) /*设置数据位数*/
	{   
	case 7:		
		options.c_cflag |= CS7; 
		break;
	case 8:     
		options.c_cflag |= CS8;
		break;   
	default:    
		fprintf(stderr,"Unsupported data size\n"); return (FALSE);  
	}
switch (parity) 
{   
	case 'n':
	case 'N':    
		options.c_cflag &= ~PARENB;   /* Clear parity enable */
		options.c_iflag &= ~INPCK;     /* Enable parity checking */ 
		break;  
	case 'o':   
	case 'O':     
		options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/  
		options.c_iflag |= INPCK;             /* Disnable parity checking */ 
		break;  
	case 'e':  
	case 'E':   
		options.c_cflag |= PARENB;     /* Enable parity */    
		options.c_cflag &= ~PARODD;   /* 转换为偶效验*/     
		options.c_iflag |= INPCK;       /* Disnable parity checking */
		break;
	case 'S': 
	case 's':  /*as no parity*/   
	    options.c_cflag &= ~PARENB;
		options.c_cflag &= ~CSTOPB;break;  
	default:   
		fprintf(stderr,"Unsupported parity\n");    
		return (FALSE);  
	}  
/* 设置停止位*/  
switch (stopbits)
{   
	case 1:    
		options.c_cflag &= ~CSTOPB;  
		break;  
	case 2:    
		options.c_cflag |= CSTOPB;  
	   break;
	default:    
		 fprintf(stderr,"Unsupported stop bits\n");  
		 return (FALSE); 
} 
/* Set input parity option */ 
if (parity != 'n')   
	options.c_iflag |= INPCK; 
tcflush(fd,TCIFLUSH);
options.c_cc[VTIME] = 150; /* 设置超时15 seconds*/   
options.c_cc[VMIN] = 0; /* Update the options and do it NOW */
if (tcsetattr(fd,TCSANOW,&options) != 0)   
{ 
	perror("SetupSerial 3");   
	return (FALSE);  
} 
return (TRUE);  
}

需要注意的是:

如果不是开发终端之类的,只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通讯,设置方式如下:

options.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/
options.c_oflag  &= ~OPOST;   /*Output*/


读写设备

设置好串口之后,读写串口就很容易了,把串口当作文件读写就是。

  • 发送数据
    char  buffer[1024];
    int    nByte;
    int    longth;
    nByte = write(fd, buffer , longth);
    

  • 读取串口数据

    使用文件操作read函数读取,如果设置为原始模式(Raw Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数。

    可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操

  • m=read(fd, buff, sizeof(buff));

  • 参数fd表示文件描述符;

  • buff表示读取的数据存放的位置;

  • sizeof表示读取数据的大小,指读取了多少个字节;

  • m表示read函数的返回值;是int型的,表示实际串口收到的字符数;

 
  • 关闭串口

    关闭串口就是关闭文件。

    close(fd);






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值