注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

学习笔记

正确的方法如同学习书法,开始的时候要临摹,临摹好了然后创造自己的风格。

 
 
 

日志

 
 

[Linux笔记]Linux串口编程  

2012-01-13 20:25:09|  分类: Linux |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

1. 串口概述
    用户常见的数据通信的基本方式可分为并行通信与串行通信两种。
*   并行通信是指利用多条数据传输线将一个资料的各位同时传输。它的特点是传输速度快,适用于短距离通信,但要求传输速度较高的应用场合。
*   串行通信是指利用一条数据传输线将资料一位一位地顺序传送。它的特点是通信线路简单,利用简单的线缆就可实现通信,降低成本,适用于远距离通信,但传输速度慢的应用场合。
    S3C2410X内部具有2个独立的UART控制器,每个控制器都可以工作在Interrupt(终端)模式或者DMA(直接内存访问)模式。同时,每个UART均具有16字节的FIFO(先入先出寄存器),支持的最高波特率可达到230.5Kbps。UART的操作主要可分为以下几个部分:资料发送、资料接受、产生中断、产生波特率、Loopback模式、红外模式以及自动流控模式。
    串口参数的配置,一般包括波特率、起始位数量、数据位数量、停止位数量和流控协议。
    在Linux下,所以的设备文件一般都位于"/dev"下,其中串口1、串口2对应的设备名依次为"/dev/ttyS0"、"/dev/ttyS1"。

2. 串口设置
    串口的设置包括波特率的设置,校验位和停止位的设置。
    串口的设置主要是设置struct termios结构体的各成员值,如下所示:
#include <termios.h>
struct termio
{
    unsigned short c_iflag; /*输入选项*/
    unsigned short c_oflag; /*输出选项*/
    unsigned short c_cflag; /*控制选项*/
    unsigned short c_lflag; /* local mode flags */
    unsigned short c_line; /*line discipline*/
    unsigned short c_cc[NCC]; /*control characters*/
}
    在这个结构中,最重要的是c_cflag,通过对它的赋值,用户可以设置波特率、字符大小、数据位、停止位、奇偶校验位和硬件流控等。另外c_iflag和c_cc也是比较常用的标志。在此主要对这3个成员进行详细说明。
    c_cflag支持的常量名称如下所示,其中设置波特率为相应的波特率前加上'B':
CBAUD       波特率的位掩码
B0              0波特率(放弃DTR)
B50            50波特率
B1800        1800波特率
B2400        2400波特率
B75            75 baud
B110          110 baud
B134          134.5 baud
B150          150 baud
B200          200 baud
B300          300 baud
B600          600 baud
B1200        1200 baud
B1800        1800 baud
B2400       2400 baud
B4800       4800 baud
B9600       9600 baud
B19200     19200 baud
B38400     38400 baud
B57600     57,600 baud
B76800     76,800 baud
B115200    115200波特率
EXTA         外部时钟率
EXTB         外部时钟率
CSIZE        数据位的位掩码,值为:CS5, CS6, CS7, or CS8。
CS5           5个数据位
CS6           6个数据位
CS7           7个数据位
CS8           8个数据位
CSTOPB    2个停止位(不设则是1个停止位)
CREAD      接受使能
PARENB    使能输出口的极性生成和输入口的极性检查。
PARODD   设置,则输出和输入为奇校验,清零,则输出和输入为偶校验。
HUPCL      在最后一个进程关闭设备后,拉底调制解调器控制线
CLOCAL    本地线路——不改变端口的所有者
LOBLK       Block job control output
ISIG          当收到字符INTR, QUIT, SUSP, or DSUSP时,产生相应的信号。
ICANON   启用标准模式
ECHO       回显输入字符
ECHOE     如果也设置了ICANON,则字符ERASE会擦除之前输入的字符,字符WERASE会擦除之前输入的字。
ECHOK     如果也设置了ICANON,则字符KILL会擦除当前行。
ECHONL   如果也设置了ICANON,即使没有设置ECHO也会回显字符NL。
NOFLSH   当为字符INT,QUIT,和SUDP产生信号时,禁止刷新输入输出队列。
TOSTOP   给试图对控制终端写操作的后台进程组发送信号SIGTTOU。
    c_cflag成员包含的CLOCAL和CREAD应该始终使能。这样能确保你的程序不会进行零星工作控制和挂断信号,并且接口能够读取数据。
2.1 保存原来的串口配置
    为了安全起见和以后调试程序方便,可以先保存原先串口的配置。
#include <termios.h>
#include <unistd.h>
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
    该函数得到与fd指向对象相关的参数,并将它们保存在termios_p指向的termio结构中。该函数还可以测试配置是否正确、该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为-1,其使用如下所示:
struct termios oldtio;
if(tcgetattr(fd, &oldtio)!=0)
{
    perror("SetupSerial");
    return -1;
}

2.2 激活选项有CLOCAL和CREAD
    CLOCAL和CREAD分别用于本地连接和接受使能,因此,首先要通过位掩码的方式激活这两个选项。
newtio.c_cflag |=(CLOCAL|CREAD);

2.3 设置波特率
    设置波特率有专用的函数,用户不能直接通过位掩码来操作。设置波特率的主要函数有:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio,B115200);
    一般地,用户需将输入输出函数的波特率设置成一样的。这几个函数在成功时返回0,失败时返回-1。

2.4 设置校验位和停止位
    与设置波特率不同,设置字符大小并没有现成可用的函数,需要用位掩码。一般首先去除数据位中的位掩码,在重新按要求设置。如下所示:
options.c_cflag &= ~CSIZE;/*mask the character size bits*/
options.c_cflag |= CS8;

2.5 设置奇偶校验位
    设置奇偶校验位需要用到两个termio中的成员:c_cflag和c_iflag。首先要激活c_cflag中的校验位使能标志PARENB和是否进行偶校验,同时还要激活c_iflag中的奇偶校验使能。如使能奇校验时,代码如下:
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |=  (INPCK|ISTRIP);
    而是能偶校验时的代码为:
newtio.c_iflag |=  (INPCK|ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;

2.6 设置停止位
    设置停止位是通过c_cflag中的CSTOPB来实现的。若设置停止位为1,则清除CSTOPB,若设置停止位为0,则设置CSTOPB。下面是停止位是1时的代码:
newtio.c_cflag &= ~CSTOPB;

2.7 设置最少字符和等待时间
    在对接受字符和等待时间没有特别要求的情况下,可以将其设置为0,如下所示:
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;

2.8 处理要写入的引用对象
    由于串口在重新设置之后,之前要写入的引用对象要重新处理,这时就可调用函数tcflush(fd,queue_selector)来处理要写入引用的对象。对于尚未传输的数据,或者收到的但是尚未读取的数据,其处理方法取决于queue_selector的值。其值有以下几种:
*   TCIFLUSH:刷新收到的数据但是不读。
*   TCOFLUSH:刷新写入的数据但是不传送。
*   TCIOFLUSH:同时刷新收到的和写入的数据,但是收到的数据不读,写入的数据不传送。
    使用第一种的例子如下:
tcflush(fd, TCIFLUSH);

2.9 激活配置
    在完成全部串口配置之后,要激活刚才的配置并使配置生效。这里用到的函数是tcsetattr,它的函数原型是:
tcsetattr(fd, OPTION, &newtio);
    这里的newtio就是termios类型的变量,OPTION可能的取值有以下三种:
*   TCSANOW:改变的配置立即生效
*   TCSADRAIN:改变的配置在所有写入到fd的输出被传送后生效。
*   TCSAFLUSH:改变的配置在所有写入到fd的输出被传送后生效,并且所有已接受但未读取的输入都会在改变发生前丢弃。
    该函数若调用成功则返回0,若失败则返回-1。
    如下所示:
if((tcsetattr(fd, TCSANOW,&newtio))!=0)
{
    perror("com set error");
    return -1;
}

3. 串口使用
    在配置完串口的相关属性后,就可以对串口进行打开、读写操作了。它所使用的函数和普通文件读写的函数一样,都是open、write和read。它们的区别是串口是一个终端设备,因此在函数的具体参数的选择时会有一些区别。

3.1 打开串口
    打开串口和打开普通文件一样,使用的函数也是open。如下所示:
fd = open("/dev/ttyS0", O_RDWR|O_NOCTTY|O_NDELAY);
    另外两个参数的意义如下:
*   O_NOCTTY标志用于通知Linux系统,open()函数并不会使终端设备变成进程的控制终端。
*   O_NDELAY以非阻塞的方式打开。
    接下来恢复串口的状态为阻塞状态,用于等待串口的读入。可用fcntl函数实现,如下所示:
fcntl(fd, F_SETFL, 0);
    再接着可以测试打开文件描述符是否指向一个终端设备,以进一步确认串口是否正确打开,如下所示:
#include <unistd.h>
int isatty(int fd);
返回值:成功即打开的文件描述符fd指向一个终端,返回1;失败,返回0,并设置errno。

#include <stdio.h> /* Standard input/output definitions */
#include <string.h> /* String function definitions */
#include <sys/types.h>
#include <errno.h> /* Error number definitions */
#include <sys/stat.h>
#include <fcntl.h> /* File control definitions */
#include <unistd.h> /* UNIX standard function definitions */
#include <termios.h> /* POSIX terminal control definitions */
#include <stdlib.h>

#define FALSE (-1)
#define TRUE 0

/* 设置串口通信速率
 * param: fd 类型int,打开串口的文件句柄
 * param: speed 类型int,串口速度
 * return: void
 */
int speed_arr[] = {B115200,B38400,B19200,B9600,B4800,B2400,B1800,B1200,B300};
int name_arr[] = {115200,38400,19200,9600,4800,2400,1800,1200,300};

int set_speed(int fd, int speed)
{
    int i;
 int status;
 struct termios opt;
 /*保存测试现有串口参数设置,在这里如果串口号等出错,会有相关的出错信息*/
    if(tcgetattr(fd,&opt)!=0)
    {
        perror("Get Info of Serial");
    }

 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(fd, TCSANOW, &opt);
   if(status!=0)
   {
       perror("tcsetattr fd");
    return (FALSE);
   }
   tcflush(fd, TCIOFLUSH);
  }
 }
 return 0;
}

/* 设置串口数据位,停止位和校验位
 * 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("Get Info of Serial");
        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 */
  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, "Unsupporte 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("com set error");
        return (FALSE);
    }
 return TRUE;
}

/*
 * 'open_port()' - Open serial port 0.
 *
 * Returns the file descriptor on success or -1 on error.
 */
int open_serialport(int comport)
{
 int fd=0;
 if(comport == 1)//串口1
    {
        fd = open("/dev/ttyS0", O_RDWR|O_NOCTTY|O_NDELAY);
        if(-1 == fd)
        {
            perror("Cannot Open Serial Port");
            return -1;
        }
    }
    else if(comport == 2)//串口2
    {
        fd = open("/dev/ttyS1", O_RDWR|O_NOCTTY|O_NDELAY);
        if(-1 == fd)
        {
            perror("Cannot Open Serial Port");
            return -1;
        }
    }
    else if(comport == 3)//串口3
    {
        fd = open("/dev/ttyS2", O_RDWR|O_NOCTTY|O_NDELAY);
        if(-1 == fd)
        {
            perror("Cannot Open Serial Port");
            return -1;
        }
    }

    /*恢复串口为阻塞状态*/
    if(fcntl(fd, F_SETFL, 0)<0)
        printf("fcntl failed.\n");
    else
        printf("fcntl=%d\n",fcntl(fd, F_SETFL, 0));

    /*测试是否为终端设备*/
    if(isatty(STDIN_FILENO)==0)
        printf("standard input is not a terminal device\n");
    else
        printf("isatty success\n");
 printf("fd-open=%d\n", fd);
    return fd;
}

int main()
{
    int fd;
    int nread;
    char buff[1024];
 //char *databuff=NULL;

    /*打开串口*/
 fd=open_serialport(1);
    if(fd<0)
    {
        perror("open_port error");
        return -1;
    }

    /*设置串口*/
 set_speed(fd, 115200);
 if (set_parity(fd,8,1,'N') == FALSE)
    {
        perror("Set Parity Error\n");
        return (FALSE);
    }

 /*  */
 while(1)
 {
  fgets(buff,1024,stdin);//从终端读取数据
  if((nread = write(fd, buff, 1024))>0)
     {
         //memcpy(databuff,buff,nread);
      buff[nread+1]='\0';
            printf("write data is %s\n",buff);
     }
 }
    close(fd);
    return 0;
}

参考文献:
http://www.ibm.com/developerworks/cn/linux/l-serials/index.html
http://www.cnitblog.com/zouzheng/archive/2006/12/17/20695.html
  评论这张
 
阅读(2066)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017