Linux下串口编程

在linux中,系统对上层程序人员把底层对寄存器的操作屏蔽了,提供了统一的ARI接口。我们只要通过这些统一的接口(open,write,read)来对UART0串口进行操作。
在Linux系统启动时,设备驱动将被加载。设备驱动成功加载后,将向系统反馈一个主设备号,驱动程序将根据该主设备号在/dev目录下创建对应的设备文件。程序(进程)就可以使用open,read,write函数或命令来实现对设备的访问了。
在Linux下,标准的串口设备节点名为/dev/ttyS*,如果是USB转串口,则为/dev/ttyUSB*,其中'*'代表0、1...这类数字。
一、 打开串口
1. 关于串口读写阻塞与非阻塞
对于读来说,阻塞(blocking IO)是指当前串口输入缓冲区中没有数据的时候,read函数将会阻塞在这里,直到串口输入缓冲区有数据可读取,read函数在读到了数据之后,才返回,然后整个程序才继续运行下去。对于写来说,阻塞是指当前输出缓冲区已满,或者剩下的空间小于将要写入的字节数,则write函数将会阻塞在这里,直到串口输出缓冲区剩下的空间大于或等于将要写入的字节数,执行写入操作,返回,程序才继续运行下去。
对于读来说,非阻塞(non-blocking IO)指当前输入缓冲区没有数据的时候,read函数立即返回,返回值为-1。对于写来说,非阻塞指当前串口输出缓冲区已满,或者剩下的空间小于将要写入的字节数,wirte执行写操作(并不会等待在这里),写入当前串口输出缓冲区剩下空间允许的字节数,然后返回写入的字节数。
(1)设置阻塞与非阻塞,可以在打开串口时指定,也可以在打开串口之后通过fcntl函数进行设置。如果在打开操作后还设置了fcntl函数,那么阻塞与非阻塞还是与fcntl函数设置的为准。
例如:
fd = open(devname, O_RDWR | O_NOCTTY);
上面打开串口时是以阻塞方式打开的,O_NOCTTY不将此设备分配作为此进程的控制终端,如果没有指定该标志那么所有的输入(例如键盘的终止信号(Ctrl + c)等等)将会影响程序。而像gettty(1M/8)这类程序会使用这个特性来启动一个登录进程,通常情况下用户不需要这个特性(也就是说要使用O_NOCTTY这个标志)。
fd = open(devname, O_RDWR | O_NOCTTY | O_NDELAY);
上面打开串口时是以非阻塞方式打开的,O_NDELAY标志则是告诉UNIX,这个程序并不关心DCD信号线的状态——也就是不关心端口另一端是否已经连接。如果不指定这个标志的话,除非DCD信号线上有space电压否则这个程序会一直睡眠。
(2)通过fcntl()函数设置
fcntl(fd, F_SETFL, 0); /* 设置为阻塞方式 */
比如在open时,设置了O_NOCTTY宏,那么读写都被设置为了阻塞方式,这里恢复串口的状态为阻塞状态,主要用于等待串口数据的读入。
fcntl(fd, F_SETFL, FNDELAY); /* 设置为非阻塞方式 */
(3)read阻塞配置
除了在open函数或者fcntl函数中配置阻塞方式外,read操作还有额外的配置:
options.c_cc[VMIN] = xxx;
options.c_cc[VTIME] = xxx;
这两个配置只有当设置为阻塞方式(blocking IO)时才有效,否则是无效的,这两个参数的默认值为0。
其中VMIN表示read操作时最小读取的字节数。
VTIME表示read操作时没有读到数据时等待的时间,单位为100毫秒。例如:
options.c_cc[VMIN] = 8; /* 表示最少读取8个字节 */
options.c_cc[VTIM] = 5; /* 表示超时时间为50毫秒 */
注意:
控制符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都会立即返回。  
二、 设置波特率和输入输出缓冲的清除
在设置波特率的时候,一定要保证通信双方波特率一致,否则肯定通信不成功。至于波特率的大小可以参考通信双方处理器性能来规定,还要根据经验,比如波特率跟通信距离成反比、比如波特率设置为A时通信正常,一般为了保留余量,都会设置比A小的波特率。
tcflush函数清除串口输入缓存(终端驱动已接到,但用户尚未读取)或串口输出缓存(用户已经写如缓存,但尚未发送)。函数原型:


int tcflush(int filedes,int quene)


参数解释:
filedes:设备描述符。
quene取值及含义:
   *TCIFLUSH  清除输入队列
*TCOFLUSH  清除输出队列
*TCIOFLUSH 清除输入、输出队列
举例:tcflush(fd,TCIOFLUSH);//清除输出输入缓冲
另加的说明:
在打开串口后,用户其实其实已经可以开始从串口读取数据了,但如果用户没有读取,数据将被将保存在缓冲区里。如果用户不想要开始的一段数据,或者发现缓冲区数据有误,可以使用这个函数将缓冲区清空。


详细查看配套代码。
三、 重要设置
首先需要定义一个termios结构体变量,如 struct termios options;然后调用tcgetattr( fd,&options),得到与fd指向对象的相关参数,并将它们保存于options,该函数,还可以测试配置是否正确,该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为1.
然后通过此结构体变量设置诸如数据位数、起始停止位、奇偶校验否,在代码中,一般没有设置起始位,数据位设置为8(可以是7,要根据需求,比如是unsigned char就必须能表示255,这个时候必须8位),停止位被设置为1位,无校验,也无软硬件流控制。
输出模式一定要为原始模式输出,如果在阻塞方式下,还要设置等待时间和最小接收字符,它们的关系在上述有提到。
在设置的过程中,也会根据需要调用上述讲过的tcflush函数进行对输出或输入缓冲的清除。
发送字符0X0d的时候,往往接收端得到的字符是0X0a,原因是因为在串口设置中c_iflag和c_oflag中存在从NL-CR和CR-NL的映射,即串口能把回车和换行当成同一个字符,可以进行如下设置屏蔽之:  
Opt.c_iflag &= ~ (INLCR | ICRNL | IGNCR);  
Opt.c_oflag &= ~(ONLCR | OCRNL);  
最后,会调用tcsetattr(fd,TCSANOW,&options)激活配置 (将修改后的termios数据设置到串口中)。
四、 串口的读写和关闭
关于Exynos4412处理器-Linux3.5平台系统的串口缓冲区,当开发板串口程序在运行时,向开发板发送2个字节后,隔一段时间再发2个字节,当开发板里的程序运行到read后,会读到前后两次发的四个字节,说明第二次发的不会覆盖掉第一次发的。
假如接收缓冲区里面有5个字节,这个时候对接收缓冲区读一次(比如读2个字节),如果读成功,会返回2。这个时候接收缓冲区里面就还剩3个字节,如果这个时候读10个字节,如果读成功,会返回3,因为实际有效数据只有3个。
如果对方给开发板发送0x0d,那么开发板本系统接收到的数据会是0x0a,原因是因为在串口设置中c_iflag和c_oflag中存在从NL-CR和CR-NL的映射,即串口能把回车和换行当成同一个字符,可以进行如下设置屏蔽之:  
Opt.c_iflag &= ~ (INLCR | ICRNL | IGNCR);  
Opt.c_oflag &= ~(ONLCR | OCRNL);  
在写串口时,如果第一次没有写成功,可以调用tcflush(fd,TCOFLUSH);清除输出缓冲区。




总的代码:
/***************************************************************
Uart串口测试,开发板的四个串口对应的设备文件名为ttySAC0-3。测试
时,硬件连接:RXD-TXD
注意:由于串口0已经被指定为控制台(console),所以测试时不能打开串
口0的设备文件,否则人机交互将中断。
****************************************************************/
#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>  
#include <string.h>
#include <sys/time.h>
#include <sys/ioctl.h>




int speed_arr[] = { B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300, };




/*
tcgetattr和tcsetattr这两个函数用于获取和设置串口的属性。
*/
int set_speed(int fd, int speed)
{
int   i; 
int   status; 
struct termios   Opt; /* 串口的设置主要是设置 struct termios 结构体的各成员值。*/
tcgetattr(fd, &Opt); //tcgetattr用于获取当前的串口设置到它的参数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]); 
//printf("set baud:%d\n", name_arr[i]);
status = tcsetattr(fd, TCSANOW, &Opt);  //使设置成功,TCSANOW表示立即修改设置
if  (status != 0)
{        
perror("tcsetattr fd");  
return 0;     
}    
tcflush(fd,TCIOFLUSH);   
}  
}
return 1;
}


int setUartOthers(int fd,int databits,int stopbits,int parity)

struct termios options; 
if  ( tcgetattr( fd,&options)  !=  0)

perror("SetupSerial 1");     
return 0;  
}
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 0;
}
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 parity\n");    
return 0; 
}  
/* 设置停止位*/  
switch (stopbits)
{   
case 1:    
options.c_cflag &= ~CSTOPB;  
break;  
case 2:    
options.c_cflag |= CSTOPB;  
  break;
default:    
fprintf(stderr,"Unsupported stop bits\n");  
return 0; 

/* Set input parity option */ 
if (parity != 'n')   
options.c_iflag |= INPCK; 
tcflush(fd,TCIOFLUSH);

//VTIME和VMIN之间有着复杂的关系详看文档
options.c_cc[VTIME] = 30; /* 不是控制符,只在原始模式下有效,单位为100ms*/   
options.c_cc[VMIN] = 2; /* 不是控制符,只在原始模式下有效,Update the options and do it NOW */
options.c_cc[VINTR]    = 0;       /**//* Ctrl-c */
options.c_cc[VQUIT]     = 0;   /**//* Ctrl- */
options.c_cc[VERASE]    = 0;   /**//* del */ 
options.c_cc[VKILL]    = 0;   /**//* @ */ 
options.c_cc[VEOF]     = 0;   /**//* Ctrl-d */ 
options.c_cc[VSWTC]    = 0;   /**//* '' */
options.c_cc[VSTART]   = 0;   /**//* Ctrl-q */ 
options.c_cc[VSTOP]    = 0;   /**//* Ctrl-s */ 
options.c_cc[VSUSP]    = 0;   /**//* Ctrl-z */ 
options.c_cc[VEOL]     = 0;   /**//* '' */ 
options.c_cc[VREPRINT] = 0;   /**//* Ctrl-r */
options.c_cc[VDISCARD] = 0;   /**//* Ctrl-u */ 
options.c_cc[VWERASE]  = 0;   /**//* Ctrl-w */ 
options.c_cc[VLNEXT]   = 0;   /**//* Ctrl-v */ 
options.c_cc[VEOL2]    = 0;   /**//* '' */

options.c_cflag |= (CLOCAL | CREAD);  //忽略DCD信号
options.c_cflag &= ~CRTSCTS;          //关硬件流控制
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); //原始输入模式
options.c_oflag &= ~OPOST;                       //行式输出
options.c_iflag &= ~(IXON | IXOFF | IXANY);         //关软件流控制

options.c_iflag &= ~ (INLCR | ICRNL | IGNCR); //防止接收0x0d时被变为0x0a 
options.c_oflag &= ~(ONLCR | OCRNL);  

if (tcsetattr(fd,TCSANOW,&options) != 0)   

perror("SetupSerial 3");   
return 0; 


return 1;  
}


int OpenDev(char *Dev)
{
int fd = open( Dev, O_RDWR | O_NOCTTY | O_NDELAY);  //O_RDWR表读写打开
if (-1 == fd) /*大于0表示文件描述符,-1表示打开文件时错误*/
{
perror("Can't Open Serial Port");
exit(0);
}
else
{
printf("uart open sucess!\n");
}

if(fcntl(fd, F_SETFL, FNDELAY) < 0)//非阻塞
//if(fcntl(fd, F_SETFL, 0) < 0)//阻塞
{
printf("fcntl failed!\n");
    exit(0);
  }

  if(0 == isatty(STDIN_FILENO))//测试打开的文件描述符是否应用一个终端设备,以进一步确认串口是否正确打开
{
  printf("standard input is not a terminal device\n");
        exit (0);
  }
//printf("isatty(STDIN_FILENO)=%d\n",isatty(STDIN_FILENO));
return fd;
}


int main(int argc, char **argv)
{
int uart_fd;
int nread, nwrite;

unsigned char rev_buf[128],rev_buf1[128];
memset(rev_buf, 0, sizeof rev_buf);
memset(rev_buf1, 0, sizeof rev_buf1);

char *dev  = "/dev/ttySAC3"; //串口设备0-3


/*****************************************************/
uart_fd = OpenDev(dev);//打开设备


if( set_speed(uart_fd, 115200) == 1 ) //设置波特率
{
printf("set uart speed sucess!\n");
}
else
{
printf("set uart speed failed!\n");
exit (0);
}


if (setUartOthers(uart_fd,8,1,'n') == 1)  //8数据位,1停止位,无校验位
{
printf("set uart Others sucess!\n");
}
else
{
printf("Set Uart Others Error\n");
exit (0);
}
/*****************************************************/




static unsigned char uartRxBuf[]={0x0d,0xaa};/*sizeof(ws1)=11,strlen(ws1)=10*/

printf("sizeof(uartRxBuf)=%d\n",sizeof(uartRxBuf));


nwrite = write(uart_fd,uartRxBuf,sizeof(uartRxBuf));
if( nwrite == sizeof(uartRxBuf) )
{
printf("write %d Byte!\n",nwrite);/*Write会返回实际写入的字节数,如写入字符串,那么包括\0*/
}
else
{
printf("UART write Error\n");
tcflush(uart_fd,TCOFLUSH);   //清除输出缓冲区 
exit(0);
}


sleep(10);
nread=read(uart_fd, rev_buf, 2);
printf("nread=%d\n", nread);

int ii;
for(ii=0;ii<15;ii++)
   printf("%d\n", rev_buf[ii]);

//tcflush(uart_fd,TCIOFLUSH);
//tcflush(uart_fd,TCIFLUSH);  

sleep(10);
nread=read(uart_fd, rev_buf1, 10);
printf("nread=%d\n", nread);

int jj;
for(jj=0;jj<15;jj++)
   printf("%d\n", rev_buf1[jj]);

printf("\nprogram end!\n");
    close(uart_fd);


}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值