原创 LINUX串口简明解析★★★★★★不错的一篇文章,分析比较全面

2011-6-14 21:27 2503 11 7 分类: MCU/ 嵌入式
 
上周的时候,发了篇文章,关于linux串口的【当然程序师转别人的(还是发布在IBM开发者社区的嘞)】,
虽然当时能跑的通,但这周在用的时候却发现不行了,于是决定仔细研究下linux下串口这东西!
当然那个程序有问题,就要自己重新写啦。。。。。。OTZ 。。。。饿。。。我向来比较懒。。。。

当然研究过程中还是明白了不少东西的
1,解决minicom启动后才能正常读取数据的问题
2.  解决读取数据缺失的问题
3.  学习使用select函式检测串口状态信息
。。。。。。。。。。。。。。。。。。。。。。



当然这次顺带加点自己的分析
串口的配置一般也就是波特率,数据格式这些东西,而linux环境下则通过一名为termios的结构体对其进行配置
其成员如下

                     Termios Structure Members

Member Description

c_cflag

Control options

c_lflag

Line options

c_iflag

Input options

c_oflag

Output options

c_cc

Control characters

c_ispeed

Input baud (new interface)

c_ospeed

Output baud (new interface)


看英文就很容易明白了,所以我也不多说啥了。
这个结构体其实很简单,但其中每一项的配置却要复杂的多,实际上我也没有逐项的学习
只是捡有用的看了下,而下面就是关于这些知识的记录。

1.打开串口

ContractedBlock.gifExpandedBlockStart.gifCode
const char * serial_port[]={
    
"/dev/ttyS0",
    
"/dev/ttyS1",
    
"/dev/ttyS2",
    
"/dev/ttyS3",
    
"/dev/ttyS4"
};
int serial_open(int port)
{
    
int fd = open(serial_port[port], O_RDWR|O_NONBLOCK);        
    
if (-1 == fd)   
    {           
        perror(
"Can't Open Serial Port");
        exit (
-1);    
    }   
    
else 
    {
         fcntl(fd, F_SETFL, 
0);
    }
    
return fd;
}
打开时一般可以用O_RDWR ,O_NOCTTY , O_NDELAY这三个参数的组合
分别代表读写[当然有对应只读,只写的,猜都猜到了],第二个告诉系统该串口不被用来作控制终端【没弄明白,反正一般我是不用,见过跟modem通信的程序会用到】
最后那个就是不要等的意思啦。。。。。。

2.配置串口
下面的漏了个枚举量,先补上
typedef 
enum 
{
    SERIAL_8N1=0,
    SERIAL_7E1=1,
    SERIAL_7O1=2,
    SERIAL_7S1=3
}serial_format;
ContractedBlock.gifExpandedBlockStart.gifCode

void serial_format_set(int fd,serial_format format)
{
    
int status=0;
    
struct termios options; 
    
if  ( tcgetattr( fd,&options)  !=  0
    { 
        perror(
"serial fomat abnormal");     
        exit (
-1);
    }
    options.c_cflag 
&= ~CSIZE; 
    
switch (format)
    {   
        
case SERIAL_8N1:
            options.c_cflag 
&= ~PARENB;
            options.c_cflag 
&= ~CSTOPB;
            options.c_cflag 
&= ~CSIZE;
            options.c_cflag 
|= CS8;
            options.c_iflag 
&= ~(INPCK | ISTRIP);
            
break;  
        
case SERIAL_7E1:   
            options.c_cflag 
|= PARENB;
            options.c_cflag 
&= ~PARODD;
            options.c_cflag 
&= ~CSTOPB;
            options.c_cflag 
&= ~CSIZE;
            options.c_cflag 
|= CS7;
            options.c_iflag 
|= (INPCK | ISTRIP);

            
break;
        
case SERIAL_7O1:
            options.c_cflag 
|= PARENB;
            options.c_cflag 
|= PARODD;
            options.c_cflag 
&= ~CSTOPB;
            options.c_cflag 
&= ~CSIZE;
            options.c_cflag 
|= CS7;
            options.c_iflag 
|= (INPCK | ISTRIP);

            
break;
        
case SERIAL_7S1:
            options.c_cflag 
&= ~PARENB;
            options.c_cflag 
&= ~CSTOPB;
            options.c_cflag 
&= ~CSIZE;
            options.c_cflag 
|= CS8;
            options.c_iflag 
&= ~INPCK;
            options.c_iflag
|=ISTRIP;
            
break;
        
default:    
             perror(
"serial format abnormal");
             exit (
-1);
    } 
    tcflush(fd,TCIOFLUSH); 
    status
=tcsetattr(fd, TCSANOW, &options);
    
if  (status != 0
    {        
        perror(
"tcsetattr format abnormal");  
        exit(
-1);     
    }    
}
void serial_speed_set(int fd,int baudrate)
{
    
int   status; 
    
struct termios options;
    
    
/*
     * Get the current options for the port
     
*/
    
    tcgetattr(fd, 
&options);
    
    
/*
     * Set the baud rates to 19200
     
*/
    
    cfsetispeed(
&options, baudrate);
    cfsetospeed(
&options, baudrate);
    
    
/*
     * Enable the receiver and set local mode
     
*/
    
    options.c_cflag 
|= (CLOCAL | CREAD);
    
    
/*
     * Set the new options for the port
     
*/
    tcflush(fd,TCIOFLUSH); 
    status
=tcsetattr(fd, TCSANOW, &options);
    
if  (status != 0
    {        
        perror(
"tcsetattr speed abnormal");  
        exit(
-1);     
    }    

}


speed_set那个自然是用来设置波特率的
tc***attr那个参数用来获取和设置termios的信息【中间的***,自己猜吧。。。XD】
TCSANOW那个参数当然是指立即更新参数的意思
其中对speed设置具体到了输入和输出,也就意味着可以使不同的速率,另外要说的就是如果要设置9600的波特率
上面的程序中要传入的参数是B9600,他是一个被定义的数值,不是9600

format_set则是配置串口的数据格式
上面程序中配置了最常见的几种类型
比如8位数据无校验,7位数据的奇校验,偶校验以及空格校验,
下面仅分析8位无校验的最常见情形
//校验除能
options.c_cflag &= ~PARENB;
//此位除能代表一位停止位,反之代表两位停止位
options.c_cflag &= ~CSTOPB;
//清空数据宽度度屏蔽位,以便设置
options.c_cflag &= ~CSIZE;
//设置8位数据宽度 options.c_cflag |= CS8;
//相应的,对于输入的数据也除能校验使能,并且不清除第八位 options.c_iflag &= ~(INPCK | ISTRIP);







上次发的文章中的程序有问题和下面这段代码关系密切
ContractedBlock.gifExpandedBlockStart.gifCode

void serial_etc_config(int fd)
{
    
int status=0;
    
struct termios options; 
    
if  ( tcgetattr( fd,&options)  !=  0
    { 
        perror(
"serial etc abnormal");     
        exit (
-1);
    }
    
    
//no hardware flow control
    options.c_cflag &= ~CRTSCTS;
    
//no soft control
    options.c_iflag &= ~(IXON | IXOFF | IXANY);
    
//raw set_input
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);


 

    
//no output process , raw set_input
     options.c_oflag &=~OPOST;

   


    
//min character
    options.c_cc[VMIN]  = 1;
    
//time to wait for data
    options.c_cc[VTIME] = 15;

    tcflush(fd,TCIOFLUSH); 
    status
=tcsetattr(fd, TCSANOW, &options);
    
if  (status != 0
    {        
        perror(
"tcsetattr etc abnormal");  
        exit(
-1);     
    }    

}

注释其实写的很明白了,因为linux版的slickedit对中文支持不是很好【配置很麻烦,还容易卡死】,所以用英文了
这里面只说一句,就是
 options.c_lflag &=~(ICANON | ECHO | ECHOE | ISIG);
它代表为原始数据传输,而与之相对的则是标准数据传输
其实我也没弄明白到底啥区别,反正就按上面的写才能接收到数据



      

其实基本的操作都已经罗列完了,但是测试时总会出现这样那样的问题
如果直接用read读取,可能因为种种原因,独到的数据和我们期待的不一致
多半是时序上的问题,
比如你去读时数据还没准备好。。。。



      
而解决的办法,下面举一例
就是利用ioctl来获取当前串口缓冲中的信息
int wait=0;
do
{
                     
      ioctl(fd, FIONREAD, &bytes);

}
while( wait++,bytes!=bytes_needed);
而有些时候若长时间没有相应,我们还要做超时处理
这就要借用select函式了




      
fd_set         input;
struct timeval timeout;

/* Initialize the input set */
FD_ZERO(&input);
FD_SET(fd, &input);



/* Initialize the timeout structure */
timeout.tv_sec  = 10;
timeout.tv_usec = 0;

/* Do the select */
n = select(fd, input, NULL, NULL,&timeout) ;

/* See if there was an error */
if (n<0)
  perror("select failed");
else if (n == 0)
  puts("TIMEOUT");
else
{
  /* We have input */
  if (FD_ISSET(fd, input))
    process_fd();

}
这里要说明的是,超时时间中的usecond对应的是微秒,也就是microsecond
而毫秒的英文是millisecond
另外
FD_ZERO(&input);
FD_SET(fd, &input);
则分别清空了input信号集,并把fd描述符添加至该信号集




      
哎,别的不多说了,下面给出完整的回传数据测试代码
这次肯定没问题了,因为是自己写的。。。吼吼。。。。OTZ
ContractedBlock.gifExpandedBlockStart.gifCode
#include     <sys/types.h>  
#include     
<sys/stat.h>   
#include     
<sys/ioctl.h>
#include     
<sys/time.h>
#include     
<sys/select.h>
#include     
<stdio.h>    
#include     
<string.h>
#include     
<stdlib.h>     
#include     
<unistd.h>     

#include     
<fcntl.h>      
#include     
<termios.h>  
#include     
<errno.h>      

 
typedef 
enum 
{
    SERIAL_8N1
=0,
    SERIAL_7E1
=1,
    SERIAL_7O1
=2,
    SERIAL_7S1
=3
}serial_format;
const char * serial_port[]={
    
"/dev/ttyS0",
    
"/dev/ttyS1",
    
"/dev/ttyS2",
    
"/dev/ttyS3",
    
"/dev/ttyS4"
};
int serial_open(int port)
{
    
int fd = open(serial_port[port], O_RDWR|O_NONBLOCK);        
    
if (-1 == fd)   
    {           
        perror(
"Can't Open Serial Port");
        exit (
-1);    
    }   
    
else 
    {
         fcntl(fd, F_SETFL, 
0);
    }
    
return fd;
}

 
void serial_format_set(int fd,serial_format format)
{
    
int status=0;
    
struct termios options; 
    
if  ( tcgetattr( fd,&options)  !=  0
    { 
        perror(
"serial fomat abnormal");     
        exit (
-1);
    }
    options.c_cflag 
&= ~CSIZE; 
    
switch (format)
    {   
        
case SERIAL_8N1:
            options.c_cflag 
&= ~PARENB;
            options.c_cflag 
&= ~CSTOPB;
            options.c_cflag 
&= ~CSIZE;
            options.c_cflag 
|= CS8;
            options.c_iflag 
&= ~(INPCK | ISTRIP);
            
break;  
        
case SERIAL_7E1:   
            options.c_cflag 
|= PARENB;
            options.c_cflag 
&= ~PARODD;
            options.c_cflag 
&= ~CSTOPB;
            options.c_cflag 
&= ~CSIZE;
            options.c_cflag 
|= CS7;
            options.c_iflag 
|= (INPCK | ISTRIP);

            
break;
        
case SERIAL_7O1:
            options.c_cflag 
|= PARENB;
            options.c_cflag 
|= PARODD;
            options.c_cflag 
&= ~CSTOPB;
            options.c_cflag 
&= ~CSIZE;
            options.c_cflag 
|= CS7;
            options.c_iflag 
|= (INPCK | ISTRIP);

            
break;
        
case SERIAL_7S1:
            options.c_cflag 
&= ~PARENB;
            options.c_cflag 
&= ~CSTOPB;
            options.c_cflag 
&= ~CSIZE;
            options.c_cflag 
|= CS8;
            options.c_iflag 
&= ~INPCK;
            options.c_iflag
|=ISTRIP;
            
break;
        
default:    
             perror(
"serial format abnormal");
             exit (
-1);
    } 
    tcflush(fd,TCIOFLUSH); 
    status
=tcsetattr(fd, TCSANOW, &options);
    
if  (status != 0
    {        
        perror(
"tcsetattr format abnormal");  
        exit(
-1);     
    }    
}
void serial_speed_set(int fd,int baudrate)
{
    
int   status; 
    
struct termios options;
    
    
/*
     * Get the current options for the port
     
*/
    
    tcgetattr(fd, 
&options);
    
    
/*
     * Set the baud rates to 19200
     
*/
    
    cfsetispeed(
&options, baudrate);
    cfsetospeed(
&options, baudrate);
    
    
/*
     * Enable the receiver and set local mode
     
*/
    
    options.c_cflag 
|= (CLOCAL | CREAD);
    
    
/*
     * Set the new options for the port
     
*/
    tcflush(fd,TCIOFLUSH); 
    status
=tcsetattr(fd, TCSANOW, &options);
    
if  (status != 0
    {        
        perror(
"tcsetattr speed abnormal");  
        exit(
-1);     
    }    

}



void serial_etc_config(int fd)
{
    
int status=0;
    
struct termios options; 
    
if  ( tcgetattr( fd,&options)  !=  0
    { 
        perror(
"serial etc abnormal");     
        exit (
-1);
    }
    
    
//no hardware flow control
    options.c_cflag &= ~CRTSCTS;
    
//no soft control
    options.c_iflag &=~(IXON | IXOFF | IXANY);
    
//raw set_input
    options.c_lflag &=~(ICANON | ECHO | ECHOE | ISIG);


 

    
//no output process , raw set_input
     options.c_oflag &=~OPOST;

   


    
//min character
    options.c_cc[VMIN]  = 1;
    
//time to wait for data
    options.c_cc[VTIME] = 15;

    tcflush(fd,TCIOFLUSH); 
    status
=tcsetattr(fd, TCSANOW, &options);
    
if  (status != 0
    {        
        perror(
"tcsetattr etc abnormal");  
        exit(
-1);     
    }    

}

void serial_config(int fd,int baudrate,serial_format format)
{
    serial_speed_set(fd,baudrate);
    serial_format_set(fd,format);
    serial_etc_config(fd);
}



int main(int argc, char **argv)
{

    fd_set  set_input;
    
int fd;
    
char buff[50]={'\0',};
    
struct  timeval timeout;
    
int ret_select=0;
    
int bytes=0;
    
    
int write_num=0;
    
int read_num=0;
    
int wait=0;

    fd 
= serial_open(0);
    serial_config(fd,B9600,SERIAL_8N1);
    
 
  
    FD_ZERO(
&set_input);
    FD_SET(fd,
&set_input);

     

    
int loop_test=5
    
while (loop_test) 
    {   
        printf(
"Loop %d\n",loop_test);
        loop_test
--;
        
         
//tcflush(fd,TCIOFLUSH);

        
        
if(loop_test%2)
        {
            write_num
=write(fd,"hello \nlinux world !\n",strlen("hello \nlinux world !\n"));
        }
        
else
        {
            write_num
=write(fd,"hello \nserial port !\n",strlen("hello \nserial port !\n"));
        }
 
        printf(
"now %d bytes has been written to the serial port\n",write_num);
       
        timeout.tv_sec 
=1;
        timeout.tv_usec 
=0;
        ret_select
= select(fd+1,&set_input, NULL,NULL,&timeout);
        
/* See if there was an error */
        
if (ret_select<0)
          perror(
"select failed");
        
else if (ret_select == 0)
          printf(
"timeout\n");
        
else
        {
            
              
              
              
/* We have set_input */
             
if (FD_ISSET(fd,&set_input))
              {
                   
                  bytes
=0;
                  wait
=0;
                  
do
                  {
                     
                      ioctl(fd, FIONREAD, 
&bytes);

                  }
                  
while( wait++,bytes!=write_num);
                  printf(
"now %d bytes is availble to read\n",bytes);
                  printf(
"delay %d times for read\n",wait);
                  read_num 
= read(fd, buff, bytes);
                  printf(
"now %d bytes has been read from serial\n",read_num);
                  buff[bytes] 
= '\0';          
                  printf( 
"%s", buff);  
              }    
        }
        printf(
"************\n");
    }

    close(fd);  
    exit (
0);















      







 
实际测试截图,截取自linux版slickedit的output窗口



串口接法图【使用跳线帽】

文章评论0条评论)

登录后参与讨论
我要评论
0
11
关闭 站长推荐上一条 /2 下一条