Linux串口编程

  • Linux串口编程已关闭评论
  • 146 次浏览
  • A+
所属分类:linux技术
摘要

在嵌入式应用领域中,串口是最为常见的一种硬件通信接口。因为其具备协议简单,硬件电路精简等优势使得串口基本成为MCU、计算机或嵌入式产品的标配接口。本文仅介绍在Linux系统下串口编程需要使用的API和一些应用技巧,关于串口的背景知识介绍,以及Windows系统下串口编程读者可以移步至其他文章。

在嵌入式应用领域中,串口是最为常见的一种硬件通信接口。因为其具备协议简单,硬件电路精简等优势使得串口基本成为MCU、计算机或嵌入式产品的标配接口。本文仅介绍在Linux系统下串口编程需要使用的API和一些应用技巧,关于串口的背景知识介绍,以及Windows系统下串口编程读者可以移步至其他文章。

Linux系统下串口的操作主要分为如下部分:

  • 串口打开、关闭
  • 串口参数设置
  • 串口数据发送与接收
  • 串口MODEM信号设置与读取
  • 串口Break信号发送

 

可以熟练掌握并应用以上串口功能已经可以应对Linux系统上串口应用的大多数场景了,针对更高级的串口用法可以阅读《Linux串口编程-进阶篇》,包含Linux系统使用非标准波特率、同步等待Modem信号变化、串口参数VTIME和VMIN的作用、RS485串口功能开关等。为方便用户使用我们将以上串口操作均封装成了独立的函数,可以极大的节约开发时间。

1、串口打开

    /**      * libtty_open - open tty device      * @devname: the device name to open      *      * In this demo device is opened blocked, you could modify it at will.      */     static int libtty_open(const char *devname)     {     	int fd = open(devname, O_RDWR | O_NOCTTY | O_NDELAY);     	int flags = 0;           	if (fd < 0) {     		perror("open device failed");     		return -1;     	}         /* 恢复串口为阻塞状态 */     	flags = fcntl(fd, F_GETFL, 0);     	flags &= ~O_NONBLOCK;     	if (fcntl(fd, F_SETFL, flags) < 0) {     		printf("fcntl failed.n");     		return -1;     	}         /* 测试该设备是否为tty设备 */     	if (isatty(fd) == 0) {     		printf("not tty device.n");     		return -1;     	} else     		printf("tty device test ok.n");           	return fd;     }

 

Note:

  • devname 参数为设备绝对路径,如:“/dev/ttyUSB0”
  • O_NOCTTY标志用于通知系统,这个程序不会成为对应这个设备的控制终端。如果没有指定这个标志,那么任何一个输入(如SIGINT等)都将会影响用户的进程;
  • O_NDELAY标志与O_NONBLOCK 等效,但这里不仅仅是设置为非阻塞,还用于通知系统,这个程序不关心 DCD 信号线所处的状态(即与设备相连的另一端是否激活或者停止)。如果用户指定了这一标志,则进程将会一直处在休眠状态,直到 DCD 信号线被激活;

2、串口关闭

    /**      * libtty_close - close tty device      * @fd: the device handle      *      * The function return 0 if success, others if fail.      */     static int libtty_close(int fd)     {     	return close(fd);     }

3、串口设置

    /**      * libtty_setopt - config tty device      * @fd: device handle      * @speed: baud rate to set      * @databits: data bits to set      * @stopbits: stop bits to set      * @parity: parity to set      * @hardflow: hardflow to set      *      * The function return 0 if success, or -1 if fail.      */     static int libtty_setopt(int fd, int speed, int databits, int stopbits, char parity, char hardflow)     {     	struct termios newtio;     	struct termios oldtio;     	int i;           	bzero(&newtio, sizeof(newtio));     	bzero(&oldtio, sizeof(oldtio));               /* 先保存之前配置,以防后续步骤出错无法恢复 */     	if (tcgetattr(fd, &oldtio) != 0) {     		perror("tcgetattr");     		return -1;     	}     	newtio.c_cflag |= CLOCAL | CREAD;     	newtio.c_cflag &= ~CSIZE;               /* 串口波特率设置*/         switch (speed): {         case 1200:             cfsetspeed(&newtio, B1200);             break;         case 2400:             cfsetspeed(&newtio, B2400);             break;         case 4800:             cfsetspeed(&newtio, B4800);             break;         case 9600:             cfsetspeed(&newtio, B9600);             break;         case 19200:             cfsetspeed(&newtio, B19200);             break;         case 38400:             cfsetspeed(&newtio, B38400);             break;         case 57600:             cfsetspeed(&newtio, B57600);             break;         case 115200:             cfsetspeed(&newtio, B115200);             break;         case 230400:             cfsetspeed(&newtio, B230400);             break;         case 460800:             cfsetspeed(&newtio, B460800);             break;         case 921600:             cfsetspeed(&newtio, B921600);             break;         default:             break;         }     	for (i = 0; i < sizeof(speed_arr) / sizeof(int); i++) {     		if (speed == name_arr[i]) {           			cfsetispeed(&newtio, speed_arr[i]);      			cfsetospeed(&newtio, speed_arr[i]);        		}      	}     	/* 数据位设置 */     	switch (databits) {     	case 5:     		newtio.c_cflag |= CS5;     		break;     	case 6:     		newtio.c_cflag |= CS6;     		break;     	case 7:     		newtio.c_cflag |= CS7;     		break;     	case 8:     		newtio.c_cflag |= CS8;     		break;     	default:     		fprintf(stderr, "unsupported data sizen");     		return -1;     	}           	/* 校验位设置 */     	switch (parity) {     	case 'n':     	case 'N':     		newtio.c_cflag &= ~PARENB;    /* Clear parity enable */     		newtio.c_iflag &= ~INPCK;     /* Disable input parity check */     		break;     	case 'o':     	case 'O':     		newtio.c_cflag |= (PARODD | PARENB); /* Odd parity instead of even */     		newtio.c_iflag |= INPCK;     /* Enable input parity check */     		break;     	case 'e':     	case 'E':     		newtio.c_cflag |= PARENB;    /* Enable parity */     		newtio.c_cflag &= ~PARODD;   /* Even parity instead of odd */     		newtio.c_iflag |= INPCK;     /* Enable input parity check */     		break;     	case 'm':     	case 'M':     		newtio.c_cflag |= PARENB;    /* Enable parity */     		newtio.c_cflag |= CMSPAR;    /* Stick parity instead */     		newtio.c_cflag |= PARODD;    /* Even parity instead of odd */     		newtio.c_iflag |= INPCK;     /* Enable input parity check */     		break;     	case 's':     	case 'S':     		newtio.c_cflag |= PARENB;    /* Enable parity */     		newtio.c_cflag |= CMSPAR;    /* Stick parity instead */     		newtio.c_cflag &= ~PARODD;   /* Even parity instead of odd */     		newtio.c_iflag |= INPCK;     /* Enable input parity check */     		break;     	default:     		fprintf(stderr, "unsupported parityn");     		return -1;     	}           	/* 停止位设置 */     	switch (stopbits) {     	case 1:     		newtio.c_cflag &= ~CSTOPB;     		break;     	case 2:     		newtio.c_cflag |= CSTOPB;     		break;     	default:     		perror("unsupported stop bitsn");     		return -1;     	}               /* 硬件流控设置 */     	if (hardflow)     		newtio.c_cflag |= CRTSCTS;     	else     		newtio.c_cflag &= ~CRTSCTS;               /* 串口读操作参数设置 */     	newtio.c_cc[VTIME] = 10;	/* Time-out value (tenths of a second) [!ICANON]. */     	newtio.c_cc[VMIN] = 0;	/* Minimum number of bytes read at once [!ICANON]. */               /* 刷新串口缓冲区 */     	tcflush(fd, TCIOFLUSH);               /* 设置 */     	if (tcsetattr(fd, TCSANOW, &newtio) != 0) {     		perror("tcsetattr");     		return -1;     	}           	return 0;     }

常规串口参数的设置均可以通过如上函数进行设定,注释比较详细。

包括串口波特率、数据位、停止位、硬件流控设置等。

4、串口发送

    /**      * libtty_write - write data to uart      * @fd: file descriptor of tty device      * @buf: buffer to write      * @count: write length      *      * The function return the number of bytes written if success, others if fail.      */     static int libtty_write(int fd, char *buf, int count)     {         return write(fd, buf, count);     }

5、串口读取

    /**      * libtty_read - read data from uart      * @fd: file descriptor of tty device      * @buf: pointer to read buffer      * count: read length      *      * The function return the number of bytes read if success, others if fail.      */     static int libtty_read(int fd, char *buf, int count)     {     	return read(fd, buf, count);     }

6、串口MODEM设置

    /**      * libtty_tiocmset - modem set      * @fd: file descriptor of tty device      * @bDTR: 0 on inactive, other on DTR active      * @bRTS: 0 on inactive, other on RTS active      *      * The function return 0 if success, others if fail.      */     static int libtty_tiocmset(int fd, char bDTR, char bRTS)     {     	unsigned long controlbits = 0;           	if (bDTR)     		controlbits |= TIOCM_DTR;     	if (bRTS)     		controlbits |= TIOCM_RTS;           	return ioctl(fd, TIOCMSET, &controlbits);     }

MODEM输出信号包括DTR和RTS信号,这2个信号可以由串口应用进行控制,常用于下载或IO控制等。

针对TTL/CMOS串口,DTR和RTS无效时为高电平,有效时为高电平。

7、串口MODEM读取

    /**      * libtty_tiocmget - modem get      * @fd: file descriptor of tty device      * @modembits: pointer to modem status      *      * The function return 0 if success, others if fail.      */     static int libtty_tiocmget(int fd, unsigned long *modembits)     {     	int ret;           	ret = ioctl(fd, TIOCMGET, modembits);     	if (ret == 0) {     		if (*modembits & TIOCM_DSR)     			printf("DSR Active!n");     		if (*modembits & TIOCM_CTS)     			printf("CTS Active!n");     		if (*modembits & TIOCM_CD)     			printf("DCD Active!n");     		if (*modembits & TIOCM_RI)     			printf("RI Active!n");     	}           	return ret;     }

MODEM输出信号包括DSR、CTS、DCD和RI信号,这4个信号可以由串口应用主动读取其有效状态,常用于状态指示或同步等。

针对TTL/CMOS串口,DSR、CTS、DCD和RI无效时为高电平,有效时为高电平。

8、串口Break信号

    /**      * libtty_sendbreak - uart send break      * @fd: file descriptor of tty device      *      * Description:      *  tcsendbreak() transmits a continuous stream of zero-valued bits for a specific duration, if the  termi©      *	nal is using asynchronous serial data transmission.  If duration is zero, it transmits zero-valued bits      *	for at least 0.25 seconds, and not more that 0.5 seconds.  If duration is not zero, it sends  zero-val©      *	ued bits for some implementation-defined length of time.      *      *  If  the terminal is not using asynchronous serial data transmission, tcsendbreak() returns without tak©      *	ing any action.      */     static int libtty_sendbreak(int fd)     {     	return tcsendbreak(fd, 0);     }

串口Break信号在一些特定场景下会使用到,针对TTL/CMOS串口而言,串口Break是指串口的TXD保持一定时间的低电平。常见于一些实验仪器需要使用Break信号作为有效的开始信号。需要注意:tcsendbreak的第二个参数填0,表示串口TXD持续低电平0.25s~0.5s,如果参数为非0值,则持续参数中指定的时间。