Linux操作系统下的串口通信
本文参考自周立功等编著的《嵌入式Linux开发教程》
串口操作需要的头文件
#include
#include
#include <string.h>
#include
#include
#include
一、串口打开
串口文件可使用命令: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;
tcgetattr(fd, &opt);
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;
}
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;
}
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;
}
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_oflag &= ~ OPOST;
opt.c_cc[VTIME] = vtime;
opt.c_cc[VMIN] = vmin;
tcflush(fd, TCIOFLUSH);
return tcsetattr(fd, TCSANOW, &opt);
}
int main()
{
int fd;
int len, i, ret;
char write_buf[128];
char read_buf[128][1];
fd = open(DEV_NAME, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0)
{
perror(DEV_NAME);
return -1;
}
else printf("Open port success!
");
ret = setport(fd, 9600, 8, "1", 'N', 0, 0);
if (ret < 0)
printf("Set port attributes failed!
");
else printf("Set port attributes success!
");
printf("Sending data...
");
printf("Data to be sent :
");
fgets(write_buf, 128, stdin);
i = 0;
while(write_buf[i] != '