Linux下的串口编程

资源转自网上,原帖地址为:

http://www.laogu.com/wz_2752.htm

http://www.xxlinux.com/linux/article/development/soft/20071029/11228.html

http://blog.csdn.net/jznsmail/archive/2006/03/15/625382.aspx

  1. 串口简介 
    串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。常用的串口是 RS-232-C 接口(又称 EIA RS-232-C)它是在 1970 年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是”数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准”该标准规定采用一个 25 个脚的 DB25 连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。传输距离在码元畸变小于 4% 的情况下,传输电缆长度应为 50 英尺。
    Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍,如果要非常深入了解,建议看看本文所参考的《Serial Programming Guide for POSIX Operating》。
  2. 计算机串口的引脚说明
    序号      信号名称    符号    流向       功能
    2        发送数据    TXD    DTE→DCE    DTE发送串行数据
    3        接收数据    RXD    DTE←DCE    DTE接收串行数据
    4        请求发送    RTS    DTE→DCE    DTE请求 DCE 将线路切换到发送方式
    5        允许发送    CTS    DTE←DCE    DCE告诉 DTE 线路已接通可以发送数据
    6        数据设备就绪 DSR    DTE←DCE    DCE 准备好
    7        信号地                       信号公共地
    8        载波检测    DCD    DTE←DCE    表示 DCE 接收到远程载波
    20       数据终端就绪 DTR    DTE→DCE    DTE 准备好
    22       振铃指示    RI     DTE←DCE    表示 DCE 与线路接通,出现振铃
  3. 串口操作
    • 串口操作需要的头文件
      #include           /*标准输入输出定义*/
      #include          /*标准函数库定义*/
      #include          /*Unix 标准函数定义*/
      #include
      #include
      #include           /*文件控制定义*/
      #include         /*PPSIX 终端控制定义*/
      #include           /*错误号定义*/
    • 打开串口
      在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;        /* local mode flags */
              unsigned char  c_line;          /* line discipline */
              unsigned char  c_cc[NCC];       /* control characters */
      };

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

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

        struct  termios Opt;
        tcgetattr(fd, &Opt);
        cfsetispeed(&Opt,B19200);     /*设置为19200Bps*/
        cfsetospeed(&Opt,B19200);
        tcsetattr(fd,TCANOW,&Opt);

        设置波特率的例子函数:

        /**
        *@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);
                                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);
                        }
                }
        }</code
      • 效验位和停止位的设置:
        无效验       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; 

        设置效验的函数:

        /**
        *@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 sizen"); 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 parityn");
                        return (FALSE);
                }
        /* 设置停止位*/
        switch (stopbits)
        {
                case 1:
                        options.c_cflag &= ~CSTOPB;
                        break;
                case 2:
                        options.c_cflag  = CSTOPB;
                   break;
                default:
                         fprintf(stderr,"Unsupported stop bitsn");
                         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);
        }</code

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

        options.c_lflag  &= ~(ICANON   ECHO   ECHOE   ISIG);  /*Input*/
        options.c_oflag  &= ~OPOST;   /*Output*/
      • 读写串口
        设置好串口之后,读写串口就很容易了,把串口当作文件读写就是。
        发送数据

        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);
  4. 例子:下面是一个简单的读取串口数据的例子,使用了上面定义的一些函数和头文件
    /**********************************************************************代码说明:使用串口二测试的,发送的数据是字符,
    但是没有发送字符串结束符号,所以接收到后,后面加上了结束符号。我测试使用的是单片机发送数据到第二个串口,测试通过。
    **********************************************************************/
    #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 Errorn");
                    exit (0);
            }
    while (1) //循环读取数据
    {
            while((nread = read(fd, buff, 512))>0)
            {
                    printf("nLen %dn",nread);
                    buff[nread+1] = '''';
                    printf( "n%s", buff);
            }
    }
            //close(fd);
            // exit (0);
    }
  5. 串口配置参数详解(termios) 
    大多数系统都支持POSIX终端接口,POSIX终端通过一个termios结构来进行控制,该结构定义在termios.h文件中。termios结构如下:

    struct termios
        {
             tcflag_t  c_iflag;       /* 输入选项标志 */
             tcflag_t  c_oflag;       /* 输出选项标志 */
             tcflag_t  c_cflag;       /* 控制选项标志 */
             tcflag_t  c_lflag;       /* 本地选项标志 */
             cc_t      c_cc[NCCS];    /* 控制特性 */
        };

    c_iflag成员:

    FlagDescription
    IGNBRK忽略输入中的BREAK状态
    BRKINT如果设置了IGNBRK,将忽略BREAK。如果没有设置,但是设置了 BRKINT,那么BREAK将使得输入和输出队列被刷新,如果终端是一个前台进程组的控制终端,这个进程组中所有进程将收到SIGINT信号。如果既未设置IGNBRK也未设置BRKINT,BREAK将视为NUL同义字符,除非设置了PARMRK,这种情况下被视为序列\377
    IGNPAR忽略桢错误和奇偶校验错误
    PARMRK如果没有设置IGNPAR,在有奇偶校验错误或者桢错误的字符前插入\377。如果既没有设置IGNPAR也没有设置PARMRK,将所有奇偶校验错误或者桢错误的字符视为
    INPCK启用输入奇偶校验检测
    ISTRIP去掉第八位
    INLCR将输入的NL翻译为CR
    IGNCR忽略输入中的回车
    ICRNL将输入中的回车翻译为新行字符(除非设置了IGNCR)
    IUCLC(不属于POSIX)将输入中的大写字母映射为小写字母
    IXON启用输出的XON/XOFF流控制
    IXANY(不属于POSIX。1;XSI)允许任何字符来重新开始输出
    IXOFF启用输入的XON/XOFF流控制
    IMAXBEL(不属于POSIX)当输入队列满时响铃。LINUX没有实现该位,总是将其视为已设置

    c_oflag成员

    FlagDescription
    OPOST启用具体实现自行定义的输出
    OLCUC(不属于POSIX)将输出中的小写字母映射为大写字母
    ONLCR(XSI)将输出中的新行符映射为回车-换行
    OCRNL将输出中的回车映射为新行符
    ONOCR不在第0列输出回车
    ONLRET不输出回车
    OFILL发送填充字符作为延时
    OFDEL(不属于POSIX)填充字符是ASCII DEL(0177)。如果不设置填充字符则是ASCII NUL
    NLDLY新行延时掩码。取值为NL0和NL1
    CRDLY回车延时掩码。取值为CR0,CR1,CR2或CR3
    TABDLY水平跳格延时掩码。取值为TAB0,TAB1,TAB2,TAB3(或XTABS)。取值为TAB3,即XTABS,将扩展跳格为空格(每个跳格符填充8个空格)
    BSDLY回车延时掩码。取值为BS0或BS1.(从来没有被实现)
    VTDLY竖直跳格掩码。取值为VT0或VT1
    FFDLY进表延时掩码。取值为FF0或者FF1

    c_cflag成员

    FlagDescription
    CBAUD(不属于POSIX)波特率掩码(4+1位)
    CBAUDEX(不属于POSIX)扩展的波特率掩码(1位),包含在CBAUD中
    CSIZE字符长度掩码。取值为CS5,CS6,CS7或CS8
    CSTOPB设置两个停止位
    CREAD打开接受者
    PARENB允许输出产生奇偶信息以及输入的奇偶校验
    PARODD输入和输出是奇校验
    HUPCL在最后一个进程关闭设备后,降低MODEM控制线(挂断)
    CLOCAL忽略MODEM控制线
    LOBLK(不属于POSIX)从非当前SHELL层阻塞输出(用于sh1)
    CIBAUD(不属于POSIX)输入速度的掩码。CIBAUD各位的值与CBAUD各位相同,左移了IBSHIFT位
    CRTSCTS(不属于POSIX)启用RTS/CTS(硬件)控制流

    c_lflag成员

    FlagDescription
    ISIG当接收到字符INTR,QUIT,SUSP或DSUSP时,产生相应的信号
    XCASE(不属于POSIX;LINUX下不支持)如果同时设置了ICANON,终端只有大写。输入被转换为小写,除了以\前缀的字符。输出时,大写字符被前缀\,小写字符被转换成大写
    ECHO回显输入字符
    ECHOE如果同时设置了ICANON,字符ERASE擦除前一个输入字符,WERASE擦除前一个词
    ECHOK如果同时设置了ICANON,字符KILL删除当前行
    ECHONL如果同时设置了ICANON,回显字符NL,即使没有设置ECHO
    ECHOCTL(不属于POSIX)如果同时设置了ECHO,除了TAB,NL,START和STOP之外的ASCII控制信号被回显为x,这里X是比控制信号大0x40的ASCII码。例如字符0x08(BS)被回显为H
    ECHOPRT(不属于POSIX)如果同时设置了ICANON和IECHO,字符在删除的同时被打印
    ECHOKE(不属于POSIX)如果同时设置了ICANON,回显KILL时将删除一行中的每个字符,如同指定了ECHOE和ECHORPT一样
    DEFECHO(不属于POSIX)只在一个进程读的时候回显
    FLUSHO(不属于POSIX;LINUX不支持)输出被刷新。这个标志可以通过键入字符DISCARD来打开和关闭
    NOFLSH禁止产生SIGINT,SIGQUIT和SIGSUSP信号时刷新输入和输出队列
    TOSTOP向试图写控制终端的后台进程组发送SIGTTOU信号
    PENDIN(不属于POSIX;LINUX不支持)在读入一个字符时,输入队列中的所有字符被重新输出。(bash用他来处理typeahead)
    IEXTEN启用实现自定义的输入处理。这个标志必须与ICANON同时使用,才能解释特殊字符EOL2,LNEXT,REPRINT和WERASE,IUCLC标志才有效

    c_cc数组成员
    略(这个表太长了,需要查询的话网上有很多).

  6. 常用设置
    设置规范模式

    规范模式是面向行的输入方式,输入字符被放入用于和用户交互可以编辑的缓冲区内,直接到读入回车或者换行符号时才结束。可以通过如下方式来设置:
    option.c_lflag |= (ICANON | ECHO | ECHOE);

    设置原始输入模式
    原始输入模式是没有处理过的,当接收数据时,输入的字符在它们被接收后立即被传送,使用原始输入模式时候,一般可以选择取消ICANON,ECHO,ECHOE和ISIG选项。例如:
    option.c_lflag &= ~(ICANON | ECHO | ECHOE);

    设置输入奇偶选项
    当激活c_cflag中的奇偶校验后,应该激活输入的奇偶校验。与之相关的标志有INPCK,IGNPAR,PARMRK和ISTRIP。一般是通过选择INPCK和ISTRIP激活检验和移除奇偶位。例如:
    option.c_iflag |= (INPCK | ISTRIP);

    设置软件控制流
    软件控制流通过IXON,IXOFF和IXANY标志来设置.例如:
    option.c_iflag |=(IXON | IXOFF | IXANY);

    选择预处理输出
    通过OPOST标志来设置预处理的输出。例如:
    option.c_oflag |= OPOST;

    选择原始数据输出
    原始数据的输出通过设置c_oflag的OPOST标志。例如:
    option.c_oflag &= ~OPOST;

    设置软件流控制字符
    软件流控制字符是通过c_cc数组中的VSTART和VSTOP来设置的,一般来说,它们应该被设置城DC1(021八进制)和DC3(023八进制),分别表示ASCII码的XON和XOFF字符。

    设置读超时
    c_cc 数组中的VMIN指定了最少读取的字符数,如果设置为0,那么VTIME 就指定了读取每个字符的等待时间。VTIME是以1/10秒为单位指定接收字符的超时时间的,如果VTIME设置为0,而端口没有用open或者 fcntl设置为NONBLOCK,那么read操作将会阻塞不确定的时间

  7. 网上找到的另一串口设置程序
    设置波特率

    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)           //判断传进来是否相等
            {
                tcflush(fd, TCIOFLUSH);      //刷新输入输出缓冲
                cfsetispeed(&Opt, speed_arr);//这里分别设置
                cfsetospeed(&Opt, speed_arr);
                                             //这是立刻把bote rates设置真正写到串口中去
                status = tcsetattr(fd, TCSANOW, &Opt);
                if (status != 0)
                    perror("tcsetattr fd1"); //设置错误
                return;
            }
            tcflush(fd,TCIOFLUSH);           //同上
        }
    }

    设置数据位、校验位和停止位

    int set_Parity(int fd,int databits,int stopbits,int parity)
    {
        struct termios options;              //定义一个结构
    
        if ( tcgetattr( fd,&options) != 0)   //首先读取系统默认设置options中,必须
        {
            perror("SetupSerial 1");
            return(FALSE);
        }
    
        options.c_cflag &= ~CSIZE;           //这是设置c_cflag选项不按位数据位掩码
        switch (databits){                   /*设置数据位数*/
        case 7:
            options.c_cflag |= CS7;          //设置c_cflag选项数据位为7位
            break;
        case 8:
            options.c_cflag |= CS8;          //设置c_cflag选项数据位为8位
            break;
        default:
            fprintf(stderr,"Unsupported data size\n"); //其他的都不支持
            return (FALSE);
        }
    
        switch (parity){                    //设置奇偶校验,c_cflag和c_iflag有效
        case 'n':
        case 'N':                           //无校验 当然都不选
            options.c_cflag &= ~PARENB;     /* Clear parity enable */
            options.c_iflag &= ~INPCK;      /* Enable parity checking */
            break;
        case 'o':                           //奇校验 其中PARENB校验位有效;PARODD奇校验
        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;
        default:
                  fprintf(stderr,"Unsupported parity\n");
                  return (FALSE);
        }
    
        /* 设置停止位*/
        switch (stopbits){                  //这是设置停止位数,影响的标志是c_cflag
        case 1:
            options.c_cflag &= ~CSTOPB;     //不指明表示一位停止位
            break;
        case 2:
            options.c_cflag |= CSTOPB;      //指明CSTOPB表示两位,只有两种可能
            break;
        default:
            fprintf(stderr,"Unsupported stop bits\n");
            return (FALSE);
        }
        /* Set input parity option */
        if (parity != 'n')                  //这是设置输入是否进行校验
            options.c_iflag |= INPCK;
    
        // 这个地方是用来设置控制字符和超时参数的,一般默认即可。稍微要注意的是c_cc数组的VSTART 和 VSTOP 元素被设定成DC1 和 DC3,代表ASCII 标准的XON和XOFF字符。所以如果在传输这两个字符的时候就传不过去,这时需要把软件流控制屏蔽 options.c_iflag &= ~(IXON | IXOFF | IXANY);
    
        options.c_cc[VTIME] = 150;          // 15 seconds
        options.c_cc[VMIN] = 0;
    
        tcflush(fd,TCIFLUSH);               //刷新和立刻写进去
        if (tcsetattr(fd,TCSANOW,&options) != 0)
        {
            perror("SetupSerial 3");
            return (FALSE);
        }
        return (TRUE);
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值