Linux下的串口编程

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

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

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

http://blog.youkuaiyun.com/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);
  1. 例子:下面是一个简单的读取串口数据的例子,使用了上面定义的一些函数和头文件
    /**********************************************************************代码说明:使用串口二测试的,发送的数据是字符,
    但是没有发送字符串结束符号,所以接收到后,后面加上了结束符号。我测试使用的是单片机发送数据到第二个串口,测试通过。
    **********************************************************************/
    #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);
    }


  2. 串口配置参数详解(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数组成员
    略(这个表太长了,需要查询的话网上有很多).

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

    规范模式是面向行的输入方式,输入字符被放入用于和用户交互可以编辑的缓冲区内,直接到读入回车或者换行符号时才结束。可以通过如下方式来设置:
    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操作将会阻塞不确定的时间

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

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


1.串口操作需要的头文件

#include <stdio.h>         //标准输入输出定义
#include <stdlib.h>        //标准函数库定义
#include <unistd.h>       //Unix标准函数定义
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>          //文件控制定义
#include <termios.h>     //POSIX中断控制定义
#include <errno.h>        //错误号定义

2.打开串口

串口位于/dev中,可作为标准文件的形式打开,其中:
串口1 /dev/ttyS0
串口2 /dev/ttyS1
代码如下:
int fd;
fd = open(“/dev/ttyS0”, O_RDWR);
if(fd == -1)
{
    Perror(“串口1打开失败!”);
}
//else
    //fcntl(fd, F_SETFL, FNDELAY);
除了使用O_RDWR标志之外,通常还会使用O_NOCTTY和O_NDELAY这两个标志。
O_NOCTTY:告诉Unix这个程序不想成为“控制终端”控制的程序,不说明这个标志的话,任何输入都会影响你的程序。
O_NDELAY:告诉Unix这个程序不关心DCD信号线状态,即其他端口是否运行,不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。

3.设置波特率

最基本的串口设置包括波特率、校验位和停止位设置,且串口设置主要使用termios.h头文件中定义的termios结构,如下:
struct termios
{
   tcflag_t  c_iflag;   //输入模式标志
   tcflag_t  c_oflag;  //输出模式标志
   tcflag_t  c_cflag;  //控制模式标志
   tcflag_t  c_lflag;   //本地模式标志
   cc_t   c_line;              //line discipline
   cc_t   c_cc[NCC];    //control characters
}
代码如下:
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 SetSpeed(int fd, int speed)
{
    int i;
    struct termios Opt;    //定义termios结构

    if(tcgetattr(fd, &Opt) != 0)
    {
        perror(“tcgetattr fd”);
        return;
    }
    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]);
            if(tcsetattr(fd, TCSANOW, &Opt) != 0)
            {
                perror(“tcsetattr fd”);
                return;
            }
            tcflush(fd, TCIOFLUSH);
        }
    }
}
注意tcsetattr函数中使用的标志:
TCSANOW:立即执行而不等待数据发送或者接受完成。
TCSADRAIN:等待所有数据传递完成后执行。
TCSAFLUSH:Flush input and output buffers and make the change

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

以下是几个数据位、停止位和校验位的设置方法:(以下均为1位停止位)
8位数据位、无校验位:
Opt.c_cflag &= ~PARENB;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= CS8;
7位数据位、奇校验:
Opt.c_cflag |= PARENB;
Opt.c_cflag |= PARODD;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= CS7;
7位数据位、偶校验:
Opt.c_cflag |= PARENB;
Opt.c_cflag &= ~PARODD;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= CS7;
7位数据位、Space校验:
Opt.c_cflag &= ~PARENB;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= CS7;
代码如下:
int SetParity(int fd, int databits, int stopbits, int parity)
{
    struct termios Opt;
    if(tcgetattr(fd, &Opt) != 0)
    {
        perror("tcgetattr fd");
        return FALSE;
    }
   Opt.c_cflag |= (CLOCAL | CREAD);        //一般必设置的标志

    switch(databits)        //设置数据位数
    {
    case 7:
        Opt.c_cflag &= ~CSIZE;
        Opt.c_cflag |= CS7;
        break;
    case 8:
        Opt.c_cflag &= ~CSIZE;
        Opt.c_cflag |= CS8;
        berak;
    default:
        fprintf(stderr, "Unsupported data size.\n");
        return FALSE;
    }

    switch(parity)            //设置校验位
    {
    case 'n':
    case 'N':
        Opt.c_cflag &= ~PARENB;        //清除校验位
        Opt.c_iflag &= ~INPCK;        //enable parity checking
        break;
    case 'o':
    case 'O':
        Opt.c_cflag |= PARENB;        //enable parity
        Opt.c_cflag |= PARODD;        //奇校验
        Opt.c_iflag |= INPCK            //disable parity checking
        break;
    case 'e':
    case 'E':
        Opt.c_cflag |= PARENB;        //enable parity
        Opt.c_cflag &= ~PARODD;        //偶校验
        Opt.c_iflag |= INPCK;            //disable pairty checking
        break;
    case 's':
    case 'S':
        Opt.c_cflag &= ~PARENB;        //清除校验位
        Opt.c_cflag &= ~CSTOPB;        //??????????????
        Opt.c_iflag |= INPCK;            //disable pairty checking
        break;
    default:
        fprintf(stderr, "Unsupported parity.\n");
        return FALSE;    
    }

    switch(stopbits)        //设置停止位
    {
    case 1:
        Opt.c_cflag &= ~CSTOPB;
        break;
    case 2:
        Opt.c_cflag |= CSTOPB;
        break;
    default:
        fprintf(stderr, "Unsupported stopbits.\n");
        return FALSE;
    }

    opt.c_cflag |= (CLOCAL | CREAD);

    opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
 
    opt.c_oflag &= ~OPOST;
    opt.c_oflag &= ~(ONLCR | OCRNL);    //添加的
 
    opt.c_iflag &= ~(ICRNL | INLCR);
    opt.c_iflag &= ~(IXON | IXOFF | IXANY);    //添加的

    tcflush(fd, TCIFLUSH);
    Opt.c_cc[VTIME] = 0;        //设置超时为15sec
    Opt.c_cc[VMIN] = 0;        //Update the Opt and do it now
    if(tcsetattr(fd, TCSANOW, &Opt) != 0)
    {
        perror("tcsetattr fd");
        return FALSE;
    }

    return TRUE;
}

5.某些设置项

在第四步中我们看到一些比较特殊的设置,下面简述一下他们的作用。
c_cc数组的VSTART和VSTOP元素被设定成DC1和DC3,代表ASCII标准的XON和XOFF字符,如果在传输这两个字符的时候就传不过去,需要把软件流控制屏蔽,即:
Opt.c_iflag &= ~ (IXON | IXOFF | IXANY);
有时候,在用write发送数据时没有键入回车,信息就发送不出去,这主要是因为我们在输入输出时是按照规范模式接收到回车或换行才发送,而更多情况下我们是不必键入回车或换行的。此时应转换到行方式输入,不经处理直接发送,设置如下:
Opt.c_lflag &= ~ (ICANON | ECHO | ECHOE | ISIG);
还存在这样的情况:发送字符0X0d的时候,往往接收端得到的字符是0X0a,原因是因为在串口设置中c_iflag和c_oflag中存在从NL-CR和CR-NL的映射,即串口能把回车和换行当成同一个字符,可以进行如下设置屏蔽之:
Opt.c_iflag &= ~ (INLCR | ICRNL | IGNCR);
Opt.c_oflag &= ~(ONLCR | OCRNL);

6.读写串口

发送数据方式如下,write函数将返回写的位数或者当错误时为-1。
char buffer[1024];
int length;
int nByte;
nByte = write(fd, buffer, length);
读取数据方式如下,原始数据模式下每个read函数将返回实际串口收到的字符数,如果串口中没有字符可用,回叫将会阻塞直到以下几种情况:有字符进入;一个间隔计时器失效;错误发送。
在打开串口成功后,使用fcntl(fd, F_SETFL, FNDELAY)语句,可以使read函数立即返回而不阻塞。FNDELAY选项使read函数在串口无字符时立即返回且为0。
char buffer[1024];
int length;
int readByte;
readByte = read(fd, buffer, len);
注意:设置为原始模式传输数据的话,read函数返回的字符数是实际串口收到的字符数。Linux下直接用read读串口可能会造成堵塞,或者数据读出错误,此时可使用tcntl或者select等函数实现异步读取。用select先查询com口,再用read去读就可以避免上述错误。

7.关闭串口

串口作为文件来处理,所以一般的关闭文件函数即可:
close(fd);

8.例子

这个例子中,需要打开串口1,设置9600波特率、8位数据位、1位停止位以及空校验,之后利用while语句循环判断串口中是否可以读出数据,将串口中数据连续读出后重新写回到串口中。
该程序可与minicom联合测试。
#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>
   
main()
{
    int fd;
    int i;
    int len;
    int n = 0;      
    char read_buf[256];
    char write_buf[256];
    struct termios opt; 
    
    fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);    //默认为阻塞读方式
    if(fd == -1)
    {
        perror("open serial 0\n");
        exit(0);
    }

    tcgetattr(fd, &opt);      
    cfsetispeed(&opt, B9600);
    cfsetospeed(&opt, B9600);
    
    if(tcsetattr(fd, TCSANOW, &opt) != 0 )
    {     
       perror("tcsetattr error");
       return -1;
    }
    
    opt.c_cflag &= ~CSIZE;  
    opt.c_cflag |= CS8;   
    opt.c_cflag &= ~CSTOPB; 
    opt.c_cflag &= ~PARENB; 
    opt.c_cflag &= ~INPCK;
    opt.c_cflag |= (CLOCAL | CREAD);
 
    opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
 
    opt.c_oflag &= ~OPOST;
    opt.c_oflag &= ~(ONLCR | OCRNL);    //添加的
 
    opt.c_iflag &= ~(ICRNL | INLCR);
    opt.c_iflag &= ~(IXON | IXOFF | IXANY);    //添加的
    
    opt.c_cc[VTIME] = 0;
    opt.c_cc[VMIN] = 0;
    
    tcflush(fd, TCIOFLUSH);
 
    printf("configure complete\n");
    
    if(tcsetattr(fd, TCSANOW, &opt) != 0)
    {
        perror("serial error");
        return -1;
    }
    printf("start send and receive data\n");
  
    while(1)
    {    
        n = 0;
        len = 0;
        bzero(read_buf, sizeof(read_buf));    //类似于memset
        bzero(write_buf, sizeof(write_buf));
 
        while( (n = read(fd, read_buf, sizeof(read_buf))) > 0 )
        {
            for(i = len; i < (len + n); i++)
            {
                write_buf[i] = read_buf[i - len];
            }
            len += n;
        }
        write_buf[len] = '\0';
              
        printf("Len %d \n", len);
        printf("%s \n", write_buf);
 
        n = write(fd, write_buf, len);
        printf("write %d chars\n",n);
        
        sleep(2);
    }
}

9.附录

c_cflag用于设置控制参数,除了波特率外还包含以下内容: 
EXTA         External rate clock
EXTB         External rate clock
CSIZE         Bit mask for data bits
CS5         5个数据位
CS6         6个数据位
CS7         7个数据位
CS8         8个数据位
CSTOPB         2个停止位(清除该标志表示1个停止位
PARENB         允许校验位
PARODD         使用奇校验(清除该标志表示使用偶校验)
CREAD         Enable receiver
HUPCL         Hangup (drop DTR) on last close
CLOCAL         Local line – do not change “owner” of port
LOBLK         Block job control outpu
c_cflag标志可以定义CLOCAL和CREAD,这将确保该程序不被其他端口控制和信号干扰,同时串口驱动将读取进入的数据。CLOCAL和CREAD通常总是被是能的。

c_lflag用于设置本地模式,决定串口驱动如何处理输入字符,设置内容如下:
ISIG            Enable SIGINTR, SIGSUSP, SIGDSUSP, and SIGQUIT signals 
ICANON         Enable canonical input (else raw) 
XCASE         Map uppercase \lowercase (obsolete) 
ECHO         Enable echoing of input characters 
ECHOE         Echo erase character as BS-SP-BS 
ECHOK         Echo NL after kill character 
ECHONL         Echo NL 
NOFLSH         Disable flushing of input buffers after interrupt or quit characters 
IEXTEN         Enable extended functions 
ECHOCTL         Echo control characters as ^char and delete as ~? 
ECHOPRT         Echo erased character as character erased 
ECHOKE         BS-SP-BS entire line on line kill 
FLUSHO         Output being flushed 
PENDIN         Retype pending input at next read or input char 
TOSTOP         Send SIGTTOU for background output

c_iflag用于设置如何处理串口上接收到的数据,包含如下内容:
INPCK         Enable parity check 
IGNPAR         Ignore parity errors 
PARMRK      Mark parity errors 
ISTRIP         Strip parity bits 
IXON         Enable software flow control (outgoing) 
IXOFF         Enable software flow control (incoming) 
IXANY         Allow any character to start flow again 
IGNBRK         Ignore break condition 
BRKINT         Send a SIGINT when a break condition is detected 
INLCR         Map NL to CR 
IGNCR         Ignore CR 
ICRNL         Map CR to NL 
IUCLC         Map uppercase to lowercase 
IMAXBEL      Echo BEL on input line too long

c_oflag用于设置如何处理输出数据,包含如下内容:
OPOST         Postprocess output (not set = raw output) 
OLCUC         Map lowercase to uppercase 
ONLCR         Map NL to CR-NL 
OCRNL         Map CR to NL 
NOCR         No CR output at column 0 
ONLRET      NL performs CR function 
OFILL         Use fill characters for delay 
OFDEL         Fill character is DEL 
NLDLY         Mask for delay time needed between lines 
NL0            No delay for NLs 
NL1            Delay further output after newline for 100 milliseconds 
CRDLY      Mask for delay time needed to return carriage to left column 
CR0            No delay for CRs 
CR1            Delay after CRs depending on current column position 
CR2            Delay 100 milliseconds after sending CRs 
CR3            Delay 150 milliseconds after sending CRs 
TABDLY      Mask for delay time needed after TABs 
TAB0            No delay for TABs 
TAB1         Delay after TABs depending on current column position 
TAB2         Delay 100 milliseconds after sending TABs 
TAB3         Expand TAB characters to spaces 
BSDLY      Mask for delay time needed after BSs 
BS0         No delay for BSs 
BS1         Delay 50 milliseconds after sending BSs 
VTDLY      Mask for delay time needed after VTs 
VT0         No delay for VTs 
VT1         Delay 2 seconds after sending VTs 
FFDLY      Mask for delay time needed after FFs 
FF0         No delay for FFs 
FF1         Delay 2 seconds after sending FFs

c_cc定义了控制字符,包含以下内容:
VINTR  Interrupt  CTRL-C 
VQUIT  Quit    CTRL-Z 
VERASE   Erase    Backspace (BS) 
VKILL   Kill-line   CTRL-U 
VEOF   End-of-file   CTRL-D 
VEOL   End-of-line   Carriage return (CR) 
VEOL2   Second    end-of-line Line feed (LF) 
VMIN   Minimum number of characters to read  
VSTART   Start flow   CTRL-Q (XON) 
VSTOP   Stop flow   CTRL-S (XOFF) 
VTIME   Time to wait for data (tenths of seconds) 

注意:控制符VTIME和VMIN之间有复杂的关系。VTIME定义要求等待的时间(百毫米,通常是unsigned char变量),而VMIN定义了要求等待的最小字节数(相比之下,read函数的第三个参数指定了要求读的最大字节数)。
如果VTIME=0,VMIN=要求等待读取的最小字节数,read必须在读取了VMIN个字节的数据或者收到一个信号才会返回。
如果VTIME=时间量,VMIN=0,不管能否读取到数据,read也要等待VTIME的时间量。
如果VTIME=时间量,VMIN=要求等待读取的最小字节数,那么将从read读取第一个字节的数据时开始计时,并会在读取到VMIN个字节或者VTIME时间后返回。
如果VTIME=0,VMIN=0,不管能否读取到数据,read都会立即返回。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值