Linux操作系统下的串口通信

2019-07-12 22:47发布

Linux操作系统下的串口通信

  本文参考自周立功等编著的《嵌入式Linux开发教程》
  
  串口操作需要的头文件 #include /*标准输入输出定义*/ #include /*标准函数库定义*/ #include <string.h>/*字符数组的函数定义*/ #include /*Unix 标准函数定义*/ #include /*文件控制定义*/ #include /*PPSIX 终端控制定义*/

一、串口打开

  串口文件可使用命令:ls /dev/ttyUSB* 或 ls /dev/ttyS* 查看。本文以/dev/ttyUSB0为例。
  打开串口可使用open()函数,其原型如下:
    int open(constchar* pathname, int flags);
  参数pathname指向欲打开的文件路径字符串。下列是参数flags 所能使用的标志位:
  O_RDONLY 以只读方式打开文件
  O_WRONLY 以只写方式打开文件
  O_RDWR 以可读写方式打开文件。
  上述三种标志位是互斥的,也就是不可同时使用,但可与下列的标志位利用OR(|)运算符组合。
  O_CREAT 若欲打开的文件不存在则自动建立该文件。
  O_EXCL 如果O_CREAT也被设置,此指令会去检查文件是否存在。文件若不存在则建立该文件,否则将导致打开文件错误。此外,若O_CREAT与O_EXCL同时设置,并且欲打开的文件为符号连接,则会打开文件失败。
  O_NOCTTY 如果欲打开的文件为终端机设备时,则不会将该终端机当成进程控制终端机。
  O_TRUNC 若文件存在并且以可写的方式打开时,此标志位会令文件长度清为0,而原来存于该文件的资料也会消失。
  O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。
  O_NONBLOCK 以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。
  O_NDELAY 同O_NONBLOCK。
  O_SYNC 以同步的方式打开文件。
  O_NOFOLLOW 如果参数pathname所指的文件为一符号连接,则会令打开文件失败。
  若所有欲核查的权限都通过了检查则返回文件描述符,表示成功,只要有一个权限被禁止则返回-1。
  因此,可以以下例的方式打开串口文件,并检查是否成功打开。   fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY); if (fd < 0) { perror("/dev/ttyUSB0"); return -1; } else printf("Open port success! ");

二、串口设置

  串口驱动默认的属性(9600,8n1,无流控),而在实际应用中,往往要设置串口属性如波特率、数据位、奇偶校验、停止位等。
  进行串口编程时需要包含头文件。该文件包含了 POSIX 终端属性描述结构 struct termios,该结构如下所示: struct termios { tcflag_t c_cflag /* 控制标志 */ tcflag_t c_iflag; /* 输入标志 */ tcflag_t c_oflag; /* 输出标志 */ tcflag_t c_lflag; /* 本地标志 */ tcflag_t c_cc[NCCS]; /* 控制字符 */ }; 设置波特率 
  串口的波特率分输入波特率和输出波特率,可分别通过cfsetispeed()和 cfsetospeed()函数设置。这两个函数原型为:
    int cfsetispeed(struct termios *termptr, speed_t speed);
    int cfsetospeed(struct termios *termptr, speed_t speed);
  这两个函数若执行成功返回 0,若出错则返回-1。speed 参数为需要设置的波特率。可用如下方式设置波特率: switch(speed) { case 110: cfsetispeed(&opt, B110); cfsetospeed(&opt, B110); break; case 134: cfsetispeed(&opt, B134); cfsetospeed(&opt, B134); break; case 1200: cfsetispeed(&opt, B1200); cfsetospeed(&opt, B1200); break; case 2400: cfsetispeed(&opt, B2400); cfsetospeed(&opt, B2400); break; case 4800: cfsetispeed(&opt, B4800); cfsetospeed(&opt, B4800); break; case 9600: cfsetispeed(&opt, B9600); cfsetospeed(&opt, B9600); break; case 19200: cfsetispeed(&opt, B19200); cfsetospeed(&opt, B19200); break; case 57600: cfsetispeed(&opt, B57600); cfsetospeed(&opt, B57600); break; case 115200: cfsetispeed(&opt, B115200); cfsetospeed(&opt, B115200); break; case 460800: cfsetispeed(&opt, B460800); cfsetospeed(&opt, B460800); break; default: cfsetispeed(&opt, B9600); cfsetospeed(&opt, B9600); break; } 设置数据位 
  设置串口数据位是在 termios 结构的 c_cflag 成员上设置,可用的选项标志如下表所列: 标 志 说 明 标 志 说 明 CSIZE 数据位屏蔽 CS7 7 位数据位 CS5 5 位数据位 CS8 8 位数据位 CS6 6 位数据位 — —   可用如下方式设置数据位: opt.c_cflag &= ~ CSIZE; switch(databit) { case 8: opt.c_cflag |= CS8; break; case 7: opt.c_cflag |= CS7; break; case 6: opt.c_cflag |= CS6; break; case 5: opt.c_cflag |= CS5; break; default: opt.c_cflag |= CS8; break; } 设置奇偶校验
  Linux 的串口驱动支持无校验(‘N’)、偶校验(‘E’)和奇校验(‘O’)。设置串口的奇偶校验是在 termios 结构的 c_cflag 成员上设置,可用的选项标志如下表所列: 标 志 说 明 PARENB 进行奇偶校验 PARODD 奇校验,否则为偶校验   可用如下方式设置奇偶校验: switch(parity) { case 'N': case 'n': opt.c_cflag &= ~ PARENB; break; case 'E': case 'e': opt.c_cflag |= PARENB; opt.c_cflag &= ~ PARODD; opt.c_iflag |= (INPCK | ISTRIP); break; case 'O': case 'o': opt.c_cflag |= PARENB; opt.c_cflag |= PARODD; opt.c_iflag |= (INPCK | ISTRIP); break; default: opt.c_cflag &= ~ PARENB; } 设置停止位
  设置串口停止位是在 termios 对象的 c_cflag 成员上设置,需要用到的选项标志为CSTOPB(2 位停止位,否则为 1 位)。可用如下方式设置停止位: if (stopbit == '1') opt.c_cflag &= ~ CSTOPB; if (stopbit == '2') opt.c_cflag |= CSTOPB; 设置等待时间和最小接收字符
  调用 read()函数读取串口数据时,返回读取数据的数量需要考虑两个变量:MIN 和 TIME。MIN 和 TIME 在 termios 结构的 c_cc 成员的数组下标名为 VMIN 和 VTIME。
  MIN是指一次read调用期望返回的最小字节数。VTIME说明等待数据到达的分秒数(秒的 1/10 为分秒)。TIME 与 MIN 组合使用的具体含义分为以下四种情形:
1.当 MIN > 0,TIME > 0 时
  计时器在收到第一个字节后启动,在计时器超时之前(TIME 的时间到),若已收到 MIN个字节,则 read 返回 MIN 个字节,否则,在计时器超时后返回实际接收到的字节。
  注意:因为只有在接收到第一个字节时才开始计时,所以至少可以返回 1 个字节。这种情形中,在接到第一个字节之前,调用者阻塞。如果在调用 read 时数据已经可用,则如同在 read 后数据立即被接到一样。
2.当 MIN > 0,TIME = 0 时
  MIN 个字节完整接收后,read 才返回,这可能会造成 read 无限期地阻塞。
3.当 MIN = 0, TIME > 0 时
  TIME 为允许等待的最大时间,计时器在调用 read 时立即启动,在串口接到 1 字节数据或者计时器超时后即返回,如果是计时器超时,则返回 0。
4.当 MIN = 0,TIME = 0 时
  如果有数据可用,则 read 最多返回所要求的字节数,如果无数据可用,则 read 立即返回 0。
设置 TIME 为 150、MIN 为 255 的方法如下: opt.c_cc[VTIME] = 150; opt.c_cc[VMIN] = 255; 获取和设置串口属性
  使用函数 tcgetattr()可以获取串口设备的 termios 结构。该函数原型如下:
    int tcgetattr(int fd, struct termios *termptr);
  函数执行成功返回 0,串口设备的 termios 结构由 temptr 参数返回;若出错则返回-1。
  获得 termios 结构后,可以把串口的属性设置到 termios 结构中。串口属性设置完成后,可通过 tcsetattr()函数把新的属性设置应用到串口中。tcsetattr()函数原型如下:
    int tcsetattr(int fd, int opt, const struct termios *termptr);
  在串口驱动程序里有输入缓冲区和输出缓冲区。在改变串口属性时,缓冲区可能有数据存在,如何处理缓冲区中的数据,可通过 opt 参数实现:
  - TCSANOW: 更改立即发生;
  - TCSADRAIN: 发送了所有输出后更改才发生,若更改输出参数则应用此选项;
  - TCSAFLUSH: 发送了所有输出后更改才发生,在更改发生时未读的所有输入数据被删除(Flush)。
  上述两函数执行时,若成功则返回 0,若出错则返回-1。

三、发送数据

  往串口发送数据可通过 write()函数完成。 len = write(fd, write_buf, i); if (len < 0) printf("Send data error! "); else printf("Send success! ");

四、接收数据

  从串口接收数据可通过 read()函数完成。经过尝试发现,仅仅用一条read()语句,往往接收不到数据,或仅能接收到第一个字符。故采用以下方式读取串口数据。 char read_buf[128][1]; bzero(read_buf, sizeof(read_buf)); len = 0; while (read(fd, read_buf[len], 1) == 0); do { len++; usleep(10000); i = read(fd, read_buf[len], 1); }while (i != 0);

五、关闭串口

  当不再使用某个串口时,可用 close()函数关闭串口: close(fd);   参数 fd 为打开串口时得到的文件描述符。

六、完整程序示例

#include #include #include #include #include #include #define DEV_NAME "/dev/ttyUSB0" int setport(int fd, int baudrate, int databit, const char *stopbit, char parity, int vtime, int vmin) { struct termios opt; //get port attributes tcgetattr(fd, &opt); //set baudrate switch(baudrate) { case 110: cfsetispeed(&opt, B110); cfsetospeed(&opt, B110); break; case 134: cfsetispeed(&opt, B134); cfsetospeed(&opt, B134); break; case 1200: cfsetispeed(&opt, B1200); cfsetospeed(&opt, B1200); break; case 2400: cfsetispeed(&opt, B2400); cfsetospeed(&opt, B2400); break; case 4800: cfsetispeed(&opt, B4800); cfsetospeed(&opt, B4800); break; case 9600: cfsetispeed(&opt, B9600); cfsetospeed(&opt, B9600); break; case 19200: cfsetispeed(&opt, B19200); cfsetospeed(&opt, B19200); break; case 57600: cfsetispeed(&opt, B57600); cfsetospeed(&opt, B57600); break; case 115200: cfsetispeed(&opt, B115200); cfsetospeed(&opt, B115200); break; case 460800: cfsetispeed(&opt, B460800); cfsetospeed(&opt, B460800); break; default: cfsetispeed(&opt, B9600); cfsetospeed(&opt, B9600); break; } //set data bit opt.c_cflag &= ~ CSIZE; switch(databit) { case 8: opt.c_cflag |= CS8; break; case 7: opt.c_cflag |= CS7; break; case 6: opt.c_cflag |= CS6; break; case 5: opt.c_cflag |= CS5; break; default: opt.c_cflag |= CS8; break; } //set parity check switch(parity) { case 'N': case 'n': opt.c_cflag &= ~ PARENB; break; case 'E': case 'e': opt.c_cflag |= PARENB; opt.c_cflag &= ~ PARODD; opt.c_iflag |= (INPCK | ISTRIP); break; case 'O': case 'o': opt.c_cflag |= PARENB; opt.c_cflag |= PARODD; opt.c_iflag |= (INPCK | ISTRIP); default: opt.c_cflag &= ~ PARENB; } //set stop bit opt.c_cflag &= ~ CSTOPB; if (strcmp(stopbit, "1") == 0) opt.c_cflag &= ~ CSTOPB; if (strcmp(stopbit, "1.5") == 0) opt.c_cflag &= ~ CSTOPB; if (strcmp(stopbit, "2") == 0) opt.c_cflag |= CSTOPB; // opt.c_cflag |= (CLOCAL | CREAD); //opt.c_cflag |= (ICANON | ECHO | ECHOE); opt.c_oflag &= ~ OPOST; opt.c_cc[VTIME] = vtime; opt.c_cc[VMIN] = vmin; //empty send/receive buffer tcflush(fd, TCIOFLUSH); //set port attributes return tcsetattr(fd, TCSANOW, &opt); } int main() { int fd; int len, i, ret; char write_buf[128]; char read_buf[128][1]; //open port fd = open(DEV_NAME, O_RDWR | O_NOCTTY | O_NDELAY); if (fd < 0) { perror(DEV_NAME); return -1; } else printf("Open port success! "); //set port attributes ret = setport(fd, 9600, 8, "1", 'N', 0, 0); if (ret < 0) printf("Set port attributes failed! "); else printf("Set port attributes success! "); //send data printf("Sending data... "); printf("Data to be sent : "); fgets(write_buf, 128, stdin); i = 0; while(write_buf[i] != '