Linux串口编程
在嵌入式应用领域中,串口是最为常见的一种硬件通信接口。因为其具备协议简单,硬件电路精简等优势使得串口基本成为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 size\n");
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 parity\n");
return -1;
}
/* 停止位设置 */
switch (stopbits) {
case 1:
newtio.c_cflag &= ~CSTOPB;
break;
case 2:
newtio.c_cflag |= CSTOPB;
break;
default:
perror("unsupported stop bits\n");
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值,则持续参数中指定的时间。