Linux下串口编程入门

简介: Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍。

 

串口简介

    串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。常用的串口是 RS-232-C 接口(又称 EIA RS-232-C)它是在 1970 年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是"数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准"该标准规定采用一个 25 个脚的 DB25 连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。传输距离在码元畸变小于 4% 的情况下,传输电缆长度应为 50 英尺。

    Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍,如果要非常深入了解,建议看看本文所参考的《Serial Programming Guide for POSIX Operating Systems》

计算机串口的引脚说明

序号信号名称符号流向功能
2发送数据TXDDTE→DCEDTE发送串行数据
3接收数据RXDDTE←DCEDTE 接收串行数据
4请求发送RTSDTE→DCEDTE 请求 DCE 将线路切换到发送方式
5允许发送CTSDTE←DCEDCE 告诉 DTE 线路已接通可以发送数据
6数据设备准备好DSRDTE←DCEDCE 准备好
7信号地  信号公共地
8载波检测DCDDTE←DCE表示 DCE 接收到远程载波
20数据终端准备好DTRDTE→DCEDTE 准备好
22振铃指示RIDTE←DCE表示 DCE 与线路接通,出现振铃

串口操作

串口操作需要的头文件

#include     <stdio.h>      
#include     <stdlib.h>     
#include     <unistd.h>     
#include     <sys/types.h>  
#include     <sys/stat.h>   
#include     <fcntl.h>      
#include     <termios.h>    
#include     <errno.h>   
 


打开串口

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

串口一 为 /dev/ttyS0

串口二 为 /dev/ttyS1

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

int fd;
fd = open( "/dev/ttyS0", O_RDWR);
if (-1 == fd){ 
 
perror(" 提示错误!");
}


设置串口

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

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

  

struct termio
      unsigned short  c_iflag;               
        unsigned short  c_oflag;                       
        unsigned short  c_cflag;                       
        unsigned short  c_lflag;                       
        unsigned char  c_line;                 
        unsigned char  c_cc[NCC];   
};

 


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

波特率设置

下面是修改波特率的代码:

struct  termios Opt;
tcgetattr(fd, &Opt);
cfsetispeed(&Opt,B19200);    
cfsetospeed(&Opt,B19200);
tcsetattr(fd,TCANOW,&Opt);

 


设置波特率的例子函数:

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);    
      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);  
   
  }
}


效验位和停止位的设置:

无效验8位Option.c_cflag &= ~PARENB;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS8;
奇效验(Odd)7位Option.c_cflag |= ~PARENB;
Option.c_cflag &= ~PARODD;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS7;
偶效验(Even)7位Option.c_cflag &= ~PARENB;
Option.c_cflag |= ~PARODD;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS7;
Space效验7位Option.c_cflag &= ~PARENB;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= &~CSIZE;
Option.c_cflag |= CS8;


设置效验的函数:

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;  
                options.c_iflag &= ~INPCK;     
                break; 
        case 'o':  
        case 'O':    
                options.c_cflag |= (PARODD | PARENB);  
                options.c_iflag |= INPCK;             
                break; 
        case 'e': 
        case 'E':  
                options.c_cflag |= PARENB;        
                options.c_cflag &= ~PARODD;       
                options.c_iflag |= INPCK;      
                break;
        case 'S':
        case 's':    
            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);
}
 
if (parity != 'n')  
        options.c_iflag |= INPCK;
tcflush(fd,TCIFLUSH);
options.c_cc[VTIME] = 150;   
options.c_cc[VMIN] = 0;
if (tcsetattr(fd,TCSANOW,&options) != 0)  
{
        perror("SetupSerial 3");  
        return (FALSE); 
}
return (TRUE); 
}

需要注意的是:

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

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


读写串口

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

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

  • 读取串口数据

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

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

    char buff[1024];
    int Len;
    int readByte = read(fd,buff,Len);


关闭串口

关闭串口就是关闭文件。

close(fd);


例子

下面是一个简单的读取串口数据的例子,使用了上面定义的一些函数和头文件

#define FALSE  -1
#define TRUE   0

 

int OpenDev(char *Dev)
{
        int     fd = open( Dev, O_RDWR );         //| O_NOCTTY | O_NDELAY      
        if (-1 == fd)  
                            
                perror("Can't Open Serial Port");
                return -1;             
            
        else   
                return fd;
}
int main(int argc, char **argv){
        int fd;
        int nread;
        char buff[512];
        char *dev  = "/dev/ttyS1"; //串口二
        fd = OpenDev(dev);
        set_speed(fd,19200);
        if (set_Parity(fd,8,1,'N') == FALSE)  {
                printf("Set Parity Error\n");
                exit (0);
        }
while (1) //循环读取数据
 
        while((nread = read(fd, buff, 512))>0)
        {
                printf("\nLen %d\n",nread);
                buff[nread+1] = '\0';  
                printf( "\n%s", buff);  
        }
}
        //close(fd); 
        // exit (0);
}

 

--------------------------------------------------------------------------------------------------

 

9.1. 常用函数

    使用open()函数打开串口,open()函数有两个参数,第一个是要打开的设备名(如:/dev/ttyS0)。第二个是打开的方式。打开方式有以下三种:

  • O_RDWR,表示以读写方式打开串口。

  • O_NOCTTY,表示不成为端口的控制终端,如果没有这个选项,则任何输入(键盘按键)都会中断程序的执行。

  • O_NDELAY,表示程序不会关注DCD信号线所处的状态,即不管对端设备是运行或挂起。如果没有该选项,则程序会被设置成睡眠状态,直到DCD信号为低为止。

    成功打开串口则会返回文件描述符,打开失败则返回-1。下面是一个打开串口的示例:

    fd = open("/dev/ttyS0",O_RDWR|O_NDELAY|O_NDELAY);

    使用close()关闭打开的串口,唯一的参数是打开串口的文件描述符。下面是一个关闭串口的示例:

    close(fd); //fd是打开串口返回的文件描述符

    用write()函数向串口写数据。下面是一个向串口写数据的示例:

    n = write(fd,buff,len);

    用read()函数从串口读取数据。下面是一个从串口读数据的示例:

    n = read(fd,buff,len);

    通过fcntl()函数可以操作文件描述符,用以控制读取数据的状态。fcntl(fd,F_SETFL,0)表示没有数据则阻塞,处于等待状态,直到有数据到来;fcntl(fd,F_SETFL,FNDELAY)表示当端口没有数据时马上返回0。

 

9.2. 设置串口属性

    所有的串口属性都在一个名为termios的结构体中,要使用该结构体要包含termios.h头文件。在该头文件中还定义两个重要的函数tcgetattr()和tcsetattr(),分别用以获取和设置串口的属性。如:tcgetattr(fd,&old_termios),tcsetattr(fd,TCSANOW,&new_termios)。old_termios是旧的串口属性,new_termios是重新设置的新串口属性。tcsetattr()函数中常量的意义是:

  • TCSANOW表示新设置的串口属性马上生效。

  • TCSADRAIN表示等所有数据传送完成后才生效。

  • TCSAFLUSH表示马上清空输入和输出缓存,然后应用新的串口设置。

termios结构体内容:

成员 描述
-------------------------------------------
c_cflag 控制模式标志
c_lflag 本地模式标志
c_iflag 输入模式标志
c_oflag 输出模式标志
c_line line discipline
c_cc[NCCS] 控制字符
c_ispeed 输入波特率
c_ospeed 输出波特率

    在termios结构中的四个标志控制了输入输出的四个不同部份。输入模式标志c_iflag决定如何解释和处理接收的字符。输出模式标志c_oflag决定如何解释和处理发送到tty设备的字符。控制模式标志决定设备的一系列协议特征,这一标志只对物理设备有效。本地模式标志c_lflag决定字符在输出前如何收集和处理。

    在串口传输中,用波特率来表示传输的速度,1波特表示在1秒钟内可以传输1个码元。波特率设置可以使用cfsetispeed(&new_termios,B19200)和cfsetospeed(&new_termios,B19200)这两个函数来完成,默认的波特率为9600baud。cfsetispeed()函数用来设置输入的波特率,cfsetospeed()函数用来设置输出的波特率。B19200是termios.h头文件里定义的一个宏,表示19200的波特率。

    CLOCAL和CREAD是c_cflag成员中与速率相关的标志,在串口编程中,这两个标志一定要有效,以确保程序在突发的作业控制或挂起时,不会成为端口的占有都,同时串口的接收驱动会自动读入数据。设置方法如下:

termios_new.c_cflag |= CLOCAL; //保证程序不会成为端的占有者 termios_new.c_cflag |= CREAD; //使端口能读取输入的数据

    设置串口属性不能直接赋值,要通过对termios不同成员进行"与"和"或"操作来实现。在termios.h文件,定义了各种常量,如上面介绍的CLOCAL,CREAD。这些常量的值是掩码,通过把这些常量与termios结构成员进行逻辑操作就可实现串口属性的设置。在编程时用"|="来启用属性,用"&=~"来取消属性。

9.3. c_iflag输入标志说明

  • BRKINT和IGNBRK

    如果设置了IGNBRK,中断条件被忽略。如果没有设置IGNBRK而设置了BRKINT,中断条件清空输入输出队列中所有的数据并且向tty的前台进程组中所有进程发送一个SIGINT信号。如果这两个都没有设置,中断条件会被看作一个0字符。这时,如果设置了PARMRK,当检测到一个帧误差时将会向应用程序发送三个字节'\377''\0''\0',而不是只发送一个'\0'。

  • PARMRK和IGNPAR

    如果设定了IGNPAR,则忽略接收到的数据的奇偶检验错误或帧错误(除了前面提到的中断条件)。如果没有设置IGNPAR而设置了PARMRK,当接收到的字节存在奇偶检验错误或帧错误的时候。将向应用程序发送一个三字节的'\377''\0''\n'错误报告。其中n表示所接收到的字节。如果两者都没有设置,除了接收到的字节存在奇偶检验错误或帧误差之外的中止条件都会向应用程序发送一个单字节('\0')的报告。

  • INPCK

    如果设置,则进行奇偶校验。如果不进行奇偶检验,PARMRK和IGNPAR将对存在的奇偶校验错误不产生任何的影响。

  • ISTRIP

    如果设置,所接收到的所有字节的高位将会被去除,保证它们是一个7位的字符。

  • INLCR

    如果设置,所接收到的换行字符('\n')将会被转换成回车符('\r')。

  • IGNCR

    如果设置,则会忽略所有接收的回车符('\r')。

  • ICRNL

    如果设置,但IGNCR没有设置,接收到的回车符向应用程序发送时会变换成换行符。

  • IUCLC

    如果IUCLC和IEXTEN都设置,接收到的所有大写字母发送给应程序时都被转换成小写字母。POSIX中没有定义该标记。

  • IXOFF

    如果设置,为避免tty设备的输入缓冲区溢出,tty设备可以向终端发送停止符^S和开始符^Q,要求终端停止或重新开始向计算机发送数据。通过停止符和开始符来控制数据流的方式叫软件流控制,软件流控制方式较少用,我们主要还是用硬件流控制方式。硬件流控制在c_cflag标志中设置。

  • IXON

    如果设置,接收到^S后会停止向这个tty设备输出,接收到^Q后会恢复输出。

  • IXANY

    如果设置,则接到任何字符都会重新开始输出,而不仅仅是^Q字符。

  • IMAXBEL

    如果设置,当输入缓冲区空间满时,再接收到的任何字符就会发出警报符'\a'。POSIX中没有定义该标记。

9.4. c_oflag输出标志说明

OPOST是POSIX定义的唯一一个标志,只有设置了该标志后,其它非POSIX的输出标记才会生效。

  • OPOST

    开启该标记,后面的输出标记才会生效。否则,不会对输出数据进行处理。

  • OLCUC

    如果设置,大写字母被转换成小写字母输出。

  • ONLCR

    如果设置,在发送换行符('\n')前先发送回车符('\r')。

  • ONOCR

    如果设置,当current column为0时,回车符不会被发送也不会被处理。

  • OCRNL

    如果设置,回车符会被转换成换行符。另外,如果设置了ONLRET,则current column会被设为0.

  • ONLRET

    如果设置,当一个换行符或回车符被发送的时候,current column会被设置为0。

  • OXTABS

    如果设置,制表符会被转换成空格符。

9.5. c_cflag控制模式标志说明

  • CLOCAL

    如果设置,modem的控制线将会被忽略。如果没有设置,则open()函数会阻塞直到载波检测线宣告modem处于摘机状态为止。

  • CREAD

    只有设置了才能接收字符,该标记是一定要设置的。

  • CSIZE

    设置传输字符的位数。CS5表示每个字符5位,CS6表示每个字符6位,CS7表示每个字符7位,CS8表示每个字符8位。

  • CSTOPB

    设置停止位的位数,如果设置,则会在每帧后产生两个停止位,如果没有设置,则产生一个停止位。一般都是使用一位停止位。需要两位停止位的设备已过时了。

  • HUPCL

    如果设置,当设备最后打开的文件描述符关闭时,串口上的DTR和RTS线会减弱信号,通知Modem挂断。也就是说,当一个用户通过Modem拔号登录系统,然后注销,这时Modem会自动挂断。

  • PARENB和PARODD

    如果设置PARENB,会产生一个奇偶检验位。如果没有设置PARODD,则产生偶校验位,如果设置了PARODD,则产生奇校验位。如果没有设置PARENB,则PARODD的设置会被忽略。

  • CRTSCTS

    使用硬件流控制。在高速(19200bps或更高)传输时,使用软件流控制会使效率降低,这个时候必须使用硬件流控制。

9.6. c_cc[]控制字符说明

    只有在本地模式标志c_lflag中设置了IEXITEN时,POSIX没有定义的控制字符才能在Linux中使用。每个控制字符都对应一个按键组合(^C、^H等),但VMIN和VTIME这两个控制字符除外,它们不对应控制符。这两个控制字符只在原始模式下才有效。

  • c_cc[VINTR]

    默认对应的控制符是^C,作用是清空输入和输出队列的数据并且向tty设备的前台进程组中的每一个程序发送一个SIGINT信号,对SIGINT信号没有定义处理程序的进程会马上退出。

  • c_cc[VQUIT]

    默认对应的控制符是^\,作用是清空输入和输出队列的数据并向tty设备的前台进程组中的每一个程序发送一个SIGQUIT信号,对SIGQUIT信号没有定义处理程序的进程会马上退出。

  • c_cc[verase]

    默认对应的控制符是^H或^?,作用是在标准模式下,删除本行前一个字符,该字符在原始模式下没有作用。

  • c_cc[VKILL]

    默认对应的控制符是^U,在标准模式下,删除整行字符,该字符在原始模式下没有作用。

  • c_cc[VEOF]

    默认对应的控制符是^D,在标准模式下,使用read()返回0,标志一个文件结束。

  • c_cc[VSTOP]

    默认对应的控制字符是^S,作用是使用tty设备暂停输出直到接收到VSTART控制字符。或者,如果设备了IXANY,则等收到任何字符就开始输出。

  • c_cc[VSTART]

    默认对应的控制字符是^Q,作用是重新开始被暂停的tty设备的输出。

  • c_cc[VSUSP]

    默认对应的控制字符是^Z,使当前的前台进程接收到一个SIGTSTP信号。

  • c_cc[VEOL]和c_cc[VEOL2]

    在标准模式下,这两个下标在行的末尾加上一个换行符('\n'),标志一个行的结束,从而使用缓冲区中的数据被发送,并开始新的一行。POSIX中没有定义VEOL2。

  • c_cc[VREPRINT]

    默认对应的控制符是^R,在标准模式下,如果设置了本地模式标志ECHO,使用VERPRINT对应的控制符和换行符在本地显示,并且重新打印当前缓冲区中的字符。POSIX中没有定义VERPRINT。

  • c_cc[VWERASE]

    默认对应的控制字符是^W,在标准模式下,删除缓冲区末端的所有空格符,然后删除与之相邻的非空格符,从而起到在一行中删除前一个单词的效果。POSIX中没有定义VWERASE。

  • c_cc[VLNEXT]

    默认对应的控制符是^V,作用是让下一个字符原封不动地进入缓冲区。如果要让^V字符进入缓冲区,需要按两下^V。POSIX中没有定义VLNEXT。

    要禁用某个控制字符,只需把它设置为_POSIX_VDISABLE即可。但该常量只在Linux中有效,所以如果程序要考虑移植性的问题,请不要使用该常量。

 

9.7. c_lflag本地模式标志说明

  • ICANON

    如果设置,则启动标准模式,如果没有设置,则启动原始模式。

  • ECHO

    如果设置,则启动本地回显。如果没有设置,则除了ECHONL之外,其他以ECHO开头的标记都会失效。

  • ECHOCTL

    如果设置,则以^C的形式打印控制字符,如:按Ctrl+C显示^C,按Ctrl+?显示^?。

  • ECHOE

    如果在标准模式下设定了ECHOE标志,则当收到一个ERASE控制符时将删除前一个显示字符。

  • ECHOK和ECHOKE

    在标准模式下,当接收到一个KILL控制符,则在缓冲区中删除当前行。如果ECHOK、ECHOKE和ECHOE都没有设置,则用ECHOCTL表示的KILL字符(^U)将会在输出终端上显示,表示当前行已经被删除。

    如果已经设置了ECHOE和ECHOK,但没有设置ECHOKE,将会在输出终端显示ECHOCTL表示的KILL字符,紧接着是换行,如果设置了OPOST,将会通过OPOST处理程序进行适当的处理。

    如果ECHOK、ECHOKE和ECHOE都有设置,则会删除当前行。

    在POSIX中没有定义ECHOKE标记,在没有定义ECHOKE标记的系统中,设置ECHOK则表示同时设置了ECHOKE标志。

  • ECHONL

    如果在标准模式下设置了该标志,即使没有设置ECHO标志,换行符还是会被显示出来。

  • ECHOPRT

    如果设置,则字符会被简单地打印出来,包括各种控制字符。在POSIX中没有定义该标志。

  • ISIG

    如果设置,与INTR、QUIT和SUSP相对应的信号SIGINT、SIGQUIT和SIGTSTP会发送到tty设备的前台进程组中的所有进程。

  • NOFLSH

    一般情况下,当接收到INTR或QUIT控制符的时候会清空输入输出队列,当接收到SUSP控制符时会清空输入队列。但是如果设置了NOFLUSH标志,则所有队列都不会被清空。

  • TOSTOP

    如果设置,则当一个非前台进程组的进程试图向它的控制终端写入数据时,信号SIGTTOU会被被发送到这个进程所在的进程组。默认情况下,这个信号会使进程停止,就像收到SUSP控制符一样。

  • IEXIEN

    默认已设置,我们不应修改它。在Linux中IUCLC和几个与删除字符相关的标记都要求在设置了IEXIEN才能正常工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值