【转】Linux串口编程
参考资料
嵌入式Linux 串口应用编程
http://www.hqyj.com/news/emb174.htm
1. termios 介绍
termios是在POSIX规范中定义的标准接口,表示终端设备,包括虚拟终端、串口等。串口通过termios进行配置。
struct termios
{
unsigned short c_iflag; // 输入模式标志
unsigned short c_oflag; // 输出模式标志
unsigned short c_cflag; // 控制模式标志
unsigned short c_lflag; // 本地模式标志
unsigned char c_line; // 线路规程
unsigned char c_cc[NCC]; // 控制特性
speed_t c_ispeed; // 输入速度
speed_t c_ospeed; // 输出速度
}
- 终端有三种工作模式:规范模式(canonical mode)、非规范模式(non-canonical mode)和原始模式(raw mode)。
- 通过设置 struct termios.c_lflag 的 ICANNON 位,定义终端的工作模式。规范模式则设置ICANNON标志,非规范模式则清除ICANNON标志。前者是默认值。下面的代码设置非规范模式:
tios.c_lflag = ~(ICANON | ECHO | ECHOE | ISIG);
1.1 规范模式
- 在规范模式下,输入数据基于行进行处理。
- 在用户输入一个行结束符(回车符、EOF等)之前,系统调用read()读不到用户输入的任何字符。
- 除了EOF之外的行结束符(回车符等),与普通字符一样会被read()读到缓冲区中。
- 在规范模式中,可以进行行编辑,而且一次调用read()最多只能读取一行数据。
- 如果read()请求读取的数据字节少于当前行可读取的字节,则read()只读取被请求的字节数,剩下的字节下次再读。
1.2 非规范模式
非规范模式下,所有的输入即时有效,用户不需要另外输入行结束符,不能进行行编辑。
c_cc[VMIN](这里简称为MIN) 和 c_cc[VTIME](这里简称为TIMEOUT)的值决定read()的工作模式。TIMEOUT的单位是1/10秒。
MIN | TIMEOUT | 工作模式 |
---|---|---|
=0 | =0 | read()立即返回。若有数据可读,则读取并返回读取的字节数,否则返回0。 |
>0 | =0 | read()被阻塞,直到 MIN 个字节数据可被读取。 |
=0 | >0 | 若数据可读,或超过TIMEOUT无数据,则read()返回。 |
>0 | >0 | 若MIN个字节可读,或者超过TIMEOUT无数据,则read()返回。 |
1.3 原始模式
原始模式是一种特殊的非规范模式。这种模式下,所有输入数据以字节为单位处理。
调用cfmakeraw()函数可将终端设置为原始模式。
2. 串口配置步骤
2.1 保存老配置
使用函数tcgetattr()得到原来的配置。该函数也可以测试该串口是否可用。
struct termios tios;
tcgetattr (fd, &tios);
2.2 设置一般控制选项
CLOCAL和CREAD分别用于本地连接和接收使能。如下代码激活这两个选项。
tios.c_cflag |= CLOCAL | CREAD;
2.3 设置波特率 Baudrate
cfsetispeed()和cfsetospeed()分别设置输入和输出方向的波特率。波特率是一组定义好的常量值。通常两个方向使用同样的值。
cfsetispeed (&tios, B115200);
cfsetospeed (&tios, B115200);
2.4 设置字符大小 Character Size
字符大小可以是CS5、CS6、CS7、和CS8。CSIZE是这些值的组合。一般的做法先清除再重新设置。
tios.c_cflag &= ~CSIZE;
tios.c_cflag |= CS8;
2.5 设置奇偶校验位 Parity
分两步:
- 设置c_cflag指出是否要进行校验。
- 设置c_iflag指出输入时是否做校验。
设置奇校验:
tios.c_cflag |= (PARODD | PARENB);
tios.c_iflag |= INPCK;
设置偶校验:
tios.c_cflag |= PARENB;
tios.c_cflag &= ~PARODD;
tios.c_iflag |= INPCK;
不校验:
tios.c_cflag &= ~PARENB;
tios.c_iflag &= ~INPCK;
2.6 设置停止位 Stop Bits
设置c_cflag。
若停止位为两个bit,则激活CSTOPB:
tios.c_cflag |= CSTOPB;
若停止位为一个bit,则清除CSTOPB:
tios.c_cflag &= ~CSTOPB;
2.7 设置 MIN 和 TIMEOUT
这个取决于串口数据的特性。低延迟则需要设置一个较小的TIMEOUT;如果延时要求不高则可以设置较大TIMEOUT。MIN的值一般与最小数据包大小接近。
tios.c_cc[VTIME] = 0;
tios.c_cc[VMIN] = 0;
2.8 清除数据缓冲
在启用新的设置之前,调用tcflush()清空缓存中的残留数据。
int tcflush (int fd, int queue_selector);
queue_selector 可能的取值有以下几种:
- TCIFLUSH:清空已经接收但未读取的输入数据。
- TCOFLUSH:清空尚未传送的输出数据。
- TCIOFLUSH:前两个值的组合。
一般使用TCIOFLUSH:
tcflush (fd, TCIFLUSH);
2.9 激活新配置
使用tcsetattr()激活新配置。
tcsetattr (int fd, int optional_actions, const struct termios *termios_p);
optional_actions指定新配置生效的时机。可能的取值有以下几种:
- TCSANOW。立即生效。
- TCSADRAIN。所有输出数据都已传送。
- TCSAFLUSH。丢弃所有输出数据,所有输出数据已传送。
tcsetattr (fd, TCSANOW, &tios);
3. 示例
如下的例子中,串口工作在非规范模式下工作。
int configSerialPort (int fd, unsigned int baudrate, unsigned int databits, char parity,
unsigned int stopbits, unsigned int flowcntl)
{
struct termios tis;
int ret = 0;
// get old terminos
ret = tcgetattr (fd, &tis);
if (ret < 0)
return ret;
tis.c_cflag |= (CLOCAL | CREAD);
// baudrate
cfsetispeed (&tis, baudrate);
cfsetospeed (&tis, baudrate);
// databits
tis.c_cflag &= ~CSIZE;
switch (databits)
{
case 5: tis.c_cflag |= CS5; break;
case 6: tis.c_cflag |= CS6; break;
case 7: tis.c_cflag |= CS7; break;
case 8: tis.c_cflag |= CS8; break;
default: return -1;
}
// parity
switch (parity)
{
case 'n':
case 'N':
tis.c_cflag &= ~PARENB;
tis.c_iflag &= ~INPCK;
break;
case 'o':
case 'O':
tis.c_cflag |= (PARODD | PARENB);
tis.c_iflag |= INPCK;
break;
case 'e':
case 'E':
tis.c_cflag |= PARENB;
tis.c_cflag &= ~PARODD;
tis.c_iflag |= INPCK;
break;
default:
return -1;
}
// stopbits
switch (attr.stopbits)
{
case 1: tis.c_cflag &= ~CSTOPB; break;
case 2: tis.c_cflag |= CSTOPB; break;
default: return -1;
}
// flow control
switch (attr.flowcntl)
{
case FLOW_CNTL_NONE: tis.c_cflag &= ~CRTSCTS; break;
case FLOW_CNTL_HW: tis.c_cflag |= CRTSCTS; break;
case FLOW_CNTL_SW: tis.c_cflag |= IXON | IXOFF | IXANY; break;
default: return -1;
}
//tis.c_oflag = 0;
tis.c_lflag = ~(ICANON | ECHO | ECHOE | ISIG);
tis.c_cc[VTIME] = 0;
tis.c_cc[VMIN] = 0;
tcflush (fd, TCIFLUSH);
ret = tcsetattr (fd, TCSANOW, &tis);
if (ret < 0)
return ret;
}