Linux下串口编程

来源:互联网 发布:淘宝上靠谱的日代 编辑:程序博客网 时间:2024/05/22 00:41
origin: http://blog.csdn.net/mcgrady_tracy/article/details/23165271
参考:
1. POSIX操作系统串口编程指南2. UNIX环境高级编程在Linux下,标准的串口设备节点名为/dev/ttyS*,如果是USB转串口,则为/dev/ttyUSB*,其中'*'代表0、1...这类数字。一、访问串口1 打开串口打开串口使用open系统调用,例如:#include <stdio.h>/* Standard input/output definitions */#include <string.h>/* String function definitions */#include <unistd.h>/* UNIX standard function definitions */#include <fcntl.h>/* File control definitions */#include <errno.h>/* Error number definitions */#include <termios.h>/* POSIX terminal control definitions *//* * 'open_port()' - Open serial port 1. * * Returns the file descriptor on sueess or -1 on error. */intopen_port(void){int fd;/* File descriptor for the port */fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);if (fd == -1){/* * Could not open the port. */perror("open_port: Unable to open /dev/ttyS0 - ");}elsefcntl(fd, F_SETFL, 0);return (fd);}在上面的open系统调用中,除了O_RDWR标志外,还使用了O_NOCTTY和O_NDELAY这两个标志。O_NOCTTY:O_NOCTTY这个标志用于告知UNIX该程序不想作为该端口的”控制终端“,如果没有指定该标志那么所有的输入(例如键盘的终止信号(Ctrl + c)等等)将会影响你的程序。而像gettty(1M/8)这类程序会使用这个特性来启动一个登录进程,通常情况下用户不需要这个特性(也就是说要使用O_NOCTTY这个标志)。O_NDELAY:O_NDELAY这个标志用于告知UNIX该程序不需要关心DCD信号的状态,也就是说不需要关心端口的另一端是否已经连接。如果不指定这个标志的话,那么程序将会一直休眠直到DCD信号线上检测到有space电压。2 写数据到端口写数据使用write系统调用,例如:n = write(fd, "ATZ\r", 4);if (n < 0)fputs("write() fo 4 bytes failed!\n", stderr);write系统调用返回已发送的字节数或者发生错误时返回-1。3 从端口读取数据当端口在raw data mode操作模式下,那么read系统调用将返回从串口输入缓冲区中实际得到的字节数,如果没有数据可读,那么该系统调用将会被阻塞(block)直到有数据为止,如果超过一定时间仍然没有数据可读,那么将返回一个错误(读错误)。read函数也可以立即返回(即在没有数据可读的情况下也能立即返回,而不是被阻塞),需要做如下工作:fcntl(fd, F_SETFL, FNDELAY);FNDELAY选项的意思是read函数在没有数据可读的情况下立即返回0。需要重新回到阻塞模式(blocking)下,那么在调用fcntl()的时候不要加上FNDELAY这个选项:fcntl(fd, F_SETFL, 0);4 关闭串口关闭串口使用close系统调用,例如:close(fd);二、串口配置需要包含<termios.h>这个文件,该文件中定义了struct termios这个结构体类型。struct termios结构至少包含以下成员:tcflag_t c_iflag;/* input modes */tcflag_t c_oflag;/* output modes */tcflag_t c_cflag;/* control modes */tcflag_t c_lflag;/* local modes */cc_t c_cc[NCCS];/* control chars */1 c_cflagc_cflag成员用于控制串口波特率、数据位、校验位、停止位以及硬件流控制等等,位成员有:CBAUD波特率掩码位B0B50B75B110B134B150B200B300B600B1200B2400B4800B9600B19200B38400B57600B76800B115200EXTA外部时钟EXTB外部时钟CSIZE数据位掩码位CS5CS6CS7CS8CSTOPB2位停止位CREAD接收使能PARENB奇偶校验使能PARODD使用奇校验CLOCAL忽略终端状态行CRTSCTS硬件流控制使能位通常情况下,CLOCAL和CREAD这两个选项应该应该总是被打开的。1.1 设置波特率波特率的存储位置依赖于操作系统,在比较老接口上波特率存储在c_cflag成员中,在后来的接口中提供了c_ispeed和c_ospeed这两个成员来存储实际的波特率值,所以在设置波特率时应该使用cfsetospeed和cfsetispeed这两个函数(而不是直接赋值的方式)。例如:struct termios options;/* * Get the current options for the port... */tcgetattr(fd, &options);/* * Set the baud rates to 19200... */cfsetispeed(&options, B19200);cfsetospeed(&options, B19200);/* * Enable the receiver and set local mode... */options.c_cflag |= (CLOCAL | CREAD);/* * Set the new options for the port... */tcsetattr(fd, TCSANOW, &options);其中用到了tcgetattr和tcsetattr这两个函数用于获取和设置串口的属性。tcgetattr函数原型如下:int tcgetattr(int fd, struct termios *termios_p);tcgetattr用于获取当前的串口设置到它的参数termios_p中,而要修改串口设置则使用tcsetattr函数,原型如下:int tcsetattr(int fd, int optional_actions,      const struct termios *termios_p);其中options_actions有几个选项值:TCSANOW立即修改设置TCSADRAIN等待所有数据传输完成后才修改设置TCSAFLUSH同样需要等待,但是它是立即刷新输入、输出缓冲区,然后才修改设置。而cfsetispeed和cfsetospeed函数是专门用于设置串口波特率的,函数原型如下:int cfsetispeed(struct termios *termios_p, speed_t speed);int cfsetospeed(struct termios *termios_p, speed_t speed);1.2 设置数据位options.c_cflag &= ~CSIZE;/* Mask the character size bits */options.c_cflag |= CS8;/* Select 8 data bits */1.3 设置奇偶校验(连同数据位、停止位一起设置)无校验(8N1):options.c_cflag &= ~PARENB;options.c_cflag &= ~CSTOPB;options.c_cflag &= ~SIZE;options.c_cflag |= CS8;1.4 设置硬件流控制禁用硬件流控制:options.c_cflag &= ~CRTSCTS;2 c_lflagISIG使能SIGINTR、SIGSUSP、SIGDSUSP和SIGQUIT信号ICANON使能规范输入模式ECHO使能输入字符回显功能2.1 选择标准输入模式options.c_lflag |= (ICANON | ECHO | ECHOE);2.2 选择原始输入模式options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);那么什么是标准输入模式(Canonical Input),什么又是原始输入模式(Raw Input)呢?所谓标准输入模式是指输入是以行为单位的,可以这样理解,输入的数据最开始存储在一个缓冲区里面(但并未真正发送出去),可以使用Backspace或者Delete键来删除输入的字符,从而达到修改字符的目的,当按下回车键时,输入才真正的发送出去,这样终端程序才能接收到。通常情况下我们都是使用的是原始输入模式,也就是说输入的数据并不组成行。在标准输入模式下,系统每次返回的是一行数据,在原始输入模式下,系统又是怎样返回数据的呢?如果读一次就返回一个字节,那么系统开销就会很大,但在读数据的时候,我们也并不知道一次要读多少字节的数据,解决办法是使用c_cc数组中的VMIN和VTIME,如果已经读到了VMIN个字节的数据或者已经超过VTIME时间,系统立即返回。关于VMIN和VTIME这两个选项后面还会详细说明。3 c_iflagINPCK使能输入校验IGNPAR忽略校验错误PARMRK标记校验错误IXON使能输出软件流控制IXOFF使能输入软件流控制3.1 使能软件流控制例如:options.c_iflag |= (IXON | IXOFF | IXANY);3.2 禁用软件流控制例如:options.c_iflag &= ~(IXON | IXOFF | IXANY);4 c_oflagOPOST启用输出处理可以启用和禁止输出处理,例如:options.c_oflag |= OPOST;/* Choosing Processed Output */options.c_oflag &= ~OPOST;/* Choosing Raw Output */5 c_cc那么可能需要关注的是VMIN和VTIME这两个选项。VMIN最少读取字符数VTIME超时时间这两个参数只有当设置为阻塞模式时才有效,有以下几种可能值:5.1 MIN > 0 && TIME > 0MIN为最少读取的字符数,当读取到一个字符后,会启动一个定时器,在定时器超时事前,如果已经读取到了MIN个字符,则read返回MIN个字符。如果在接收到MIN个字符之前,定时器已经超时,则read返回已读取到的字符,注意这个定时器会在每次读取到一个字符后重新启用,即重新开始计时,而且是读取到第一个字节后才启用,也就是说超时的情况下,至少读取到一个字节数据。5.2 MIN > 0 && TIME == 0在只有读取到MIN个字符时,read才返回,可能造成read被永久阻塞。5.3 MIN == 0 && TIME > 0和第一种情况稍有不同,在接收到一个字节时或者定时器超时时,read返回。如果是超时这种情况,read返回值是0。5.4 MIN == 0 && TIME == 0这种情况下read总是立即就返回,即不会被阻塞。三、关于串口读写阻塞与非阻塞1 设置阻塞与非阻塞,可以在打开串口时指定,也可以在打开串口之后通过fcntl函数进行设置。例如:fd = open(devname, O_RDWR | O_NOCTTY);上面打开串口时是以阻塞方式打开的,如果加上O_NDELAY标志那么就是以非阻塞方式打开。fd = open(devname, O_RDWR | O_NOCTTY | O_NDELAY);2 通过fcntl()函数设置fcntl(fd, F_SETFL, 0); /* 设置为阻塞方式 */fcntl(fd, F_SETFL, FNDELAY); /* 设置为非阻塞方式 */3 阻塞与非阻塞那么阻塞与非阻塞是什么含义呢?对于读来说,阻塞(blocking IO)是指当前串口输入缓冲区中没有数据的时候,read函数将会阻塞在这里,直到串口输入缓冲区有数据可读取,read函数在读到了数据之后,才返回,然后整个程序才继续运行下去。对于写来说,阻塞是指当前输出缓冲区已满,或者剩下的空间小于将要写入的字节数,则write函数将会阻塞在这里,直到串口输出缓冲区剩下的空间大于或等于将要写入的字节数,执行写入操作,返回,程序才继续运行下去。对于读来说,非阻塞(non-blocking IO)指当前输入缓冲区没有数据的时候,read函数立即返回,返回值为0。对于写来说,非阻塞指当前串口输出缓冲区已满,或者剩下的空间小于将要写入的字节数,wirte执行写操作(并不会等待在这里),写入当前串口输出缓冲区剩下空间允许的字节数,然后返回写入的字节数。4 read阻塞配置除了在open函数或者fcntl函数中配置阻塞方式外,read操作还有额外的配置:options.c_cc[VMIN] = xxx;options.c_cc[VTIME] = xxx;这两个配置只有当设置为阻塞方式(blocking IO)时才有效,否则是无效的,这两个参数的默认值为0。其中VMIN表示read操作时最小读取的字节数。VTIME表示read操作时没有读到数据时等待的时间,单位为10毫秒。例如:options.c_cc[VMIN] = 8;/* 表示最少读取8个字节 */options.c_cc[VTIM] = 5;/* 表示超时时间为50毫秒 */5 ioctl那么对于读来说,还可以使用ioctl函数在read之前获取可读的字节数,这样也就不用关心read是阻塞与非阻塞了,例如:#include <unistd.h>#include <termios.h>int fd;int bytes;ioctl(fd, FIONREAD, &bytes);


附录:串口打开和初始化部分代码

[cpp] view plain copy
  1. #define DEVNAME "/dev/ttyUSB0"  
  2.   
  3. int serial_init(void)  
  4. {  
  5.     struct termios options;  
  6.   
  7.     /* 以非阻塞方式打开串口 */  
  8.     fd = open(DEVNAME, O_RDWR | O_NOCTTY | O_NDELAY);  
  9.     if (fd < 0) {  
  10.         printf("Open the serial port error!\n");  
  11.         return -1;  
  12.     }  
  13.   
  14.     fcntl(fd, F_SETFL, 0);  
  15.   
  16.     tcgetattr(fd, &options);  
  17.   
  18.     /* 
  19.      * Set the baud rates to 9600 
  20.      */  
  21.     cfsetispeed(&options, B9600);  
  22.     cfsetospeed(&options, B9600);  
  23.   
  24.     /* 
  25.      * Enable the receiver and set local mode 
  26.      */  
  27.     options.c_cflag |= (CLOCAL | CREAD);  
  28.   
  29.     /* 
  30.      * Select 8 data bits, 1 stop bit and no parity bit 
  31.      */  
  32.     options.c_cflag &= ~PARENB;  
  33.     options.c_cflag &= ~CSTOPB;  
  34.     options.c_cflag &= ~CSIZE;  
  35.     options.c_cflag |= CS8;  
  36.   
  37.     /* 
  38.      * Disable hardware flow control 
  39.      */  
  40.     options.c_cflag &= ~CRTSCTS;  
  41.   
  42.     /* 
  43.      * Choosing raw input 
  44.      */  
  45.     options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);  
  46.   
  47.     /* 
  48.      * Disable software flow control 
  49.      */  
  50.     options.c_iflag &= ~(IXON | IXOFF | IXANY);  
  51.   
  52.     /* 
  53.      * Choosing raw output 
  54.      */  
  55.     options.c_oflag &= ~OPOST;  
  56.   
  57.   
  58.     /* 
  59.      * Set read timeouts 
  60.      */  
  61.     options.c_cc[VMIN] = 8;  
  62.     options.c_cc[VTIME] = 10;  
  63.     //options.c_cc[VMIN] = 0;  
  64.     //options.c_cc[VTIME] = 0;  
  65.   
  66.     tcsetattr(fd, TCSANOW, &options);  
  67.   
  68.     return 0;  


原创粉丝点击