1.串口概述
常见的数据通信的基本方式可分为并行通信与串行通信两种。
- 并行通信是指利用多条数据传输线将一个字数据的各比特位同时传送。它的特点是传输速度快,适用于传输距离短且传输速度较高的通信。
- 串行通信是指利用一条传输线将数据以比特位为单位顺序传送。特点是通信线路简单,利用简单的线缆就可实现通信,降低成本,适用于传输距离长且传输速度较慢的通信。
串口是计算机一种常用的接口,常用的串口有 RS-232-C 接口。它是于 1970 年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通信的标准,它的全称是“数据终端设备(DTE)和数据通信设备(DCE)之间串行二进制数据交换接口技术标准”。该标准规定采用一个 DB25 芯 引脚的连接器或 9 芯引脚的连接器
S3C2410X 内部具有两个独立的 UART 控制器,每个控 制器都可 以工作在 Interrupt(中断)模式或者 DMA(直接存储访 问)模式。 同时,每个 UART 均具有 16 字节的 FIFO(先入先出寄 存器),支持的最高波特率可达到 230.4Kbps。UART 的操作主要可分为以下几个部分:数据发送、数据接收、产生中断、设置波 特 率 、
Loopback 模式、红外模式以及硬软流控模式。串口参数的配置读者在配置超级终端和 minicom 时也已经接触过,一般包括波特率、起始位比特数、数据位比特数、停止位比特数和流控模式。在此,可以将其配置为波特率 115200、起始位 1b、数据位 8b、停止位 1b 和无流控模式。在 Linux 中,所有的设备文件一般都位于“/dev”下,其中串口 1 和串口 2 对应的设备名依次为“/dev/ttyS0”和“/dev/ttyS1”,而且 USB 转串口的设备名通常为“/dev/ttyUSB0”和“/dev/ttyUSB1”(因版本不同该设备名会有所不同),可以查看在“/dev”下的文件以确认。
串口设置
struct termios
{
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc[NCCS]; /* control characters */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
};
终端有 3 种工作模式,分别为规范模式(canonical mode)、非规范模式(non-canonical mode)和原始模式(raw mode)。
通过在 termios 结构的 c_lflag 中设置 ICANNON 标志来定义终端是以规范模式(设置 ICANNON 标志)还是以非规范模式(清除ICANNON 标志)工作,默认情况为规范模式。
在规范模式下,所有的输入是基于行进行处理。在用户输入一个行结束符(回车符、EOF 等)之前,系统调用read()函数读不到用户输入的任何字符。除了 EOF 之外的行结束符(回车符等)与普通字符一样会被 read()函数读取到缓冲区之中。在规范模式中,行编辑是可行的,而且一次调用 read()函数最多只能读取一行数据。如果在 read()函数中被请求读取的数据字节数小于当前行可读取的字节数,则 read()函数只会读取被请求的字节数,剩下的字节下次再被读取。
在非规范模式下,所有的输入是即时有效的,不需要用户另外输入行结束符,而且不可进行行编辑。 在非规范模式下,对参数 MIN(c_cc[VMIN])
和 TIME(c_cc[VTIME])
的设置决定 read()函数的调用方式。设置可以有 4 种不同的情况。
- MIN = 0 和 TIME = 0:read()函数立即返回。若有可读数据,则读取数据并返回被读取的字节数,否则读取失败并返回 0。
- MIN > 0 和 TIME = 0:read()函数会被阻塞直到 MIN 个字节数据可被读取。
- MIN = 0 和 TIME > 0:只要有数据可读或者经过 TIME 个十分之一秒的时间,read()函数则立即返回,返回值为被读取的字节数。如果超时并且未读到数据,则 read()函数返回 0。
- MIN > 0 和 TIME > 0:当有 MIN 个字节可读或者两个输入字符之间的时间间隔超过 TIME 个十分之一秒时,read()函数才返回。因为在输入第一个字符之后系统才会启动定时器,所以在这种情况下,read()函数至少读取一个字节之后才返回。
按照严格意义来讲,原始模式是一种特殊的非规范模式。在原始模式下,所有的输入数据以字节为单位被处理。在这个模式下,终端是不可回显的,而且所有特定的终端输入/输出控制处理不可用。通过调用cfmakeraw()函数可以将终端设置为原始模式,而且该函数通过以下代码可以得到实现。
termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8;
int set_com_config(int fd, int baud_rate, int data_bits, char party, int stop_bits)
{
struct termios new_cfg, old_cfg;
int speed;
// 1.保存并测试现有串口参数设置,在这里如果串口号等出错,会有相关的出错信息
if (tcgetattr(fd, &old_cfg) != 0)
{
perror("tcgetattr");
return -1;
}
// 2.设置字符大小
new_cfg = old_cfg;
cfmakeraw(&new_cfg); // 配置为原始模式
new_cfg.c_cflag &= ~CSIZE;
// 3.设置波特率
switch (baud_rate)
{
case 2400:
speed = B2400;
break;
case 4800:
speed = B4800;
break;
case 9600:
speed = B9600;
break;
case 19200:
speed = B19200;
break;
case 38400:
speed = B38400;
break;
default:
case 115200:
speed = B115200;
break;
}
cfsetispeed(&new_cfg, speed);
cfgetospeed(&new_cfg, speed);
// 4.设置停止位
switch (data_bits)
{
case 7:
new_cfg.c_cflag |= CS7;
break;
default:
case 8:
{
new_cfg.c_cflag |= CS8;
}
break;
}
// 5.设置奇偶位校验
switch (party)
{
default:
case 'n':
case 'N':
{
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_cflag &= ~INPCK;
}
break;
case 'o':
case 'O':
{
new_cfg.c_cflag |= (PARENB | PARODD);
new_cfg.c_cflag |= INPCK;
}
break;
case 'e':
case 'E':
{
new_cfg.c_cflag |= PARENB;
new_cfg.c_cflag &= ~PARODD;
}
break;
case 's':
case 'S':
{
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_cflag &= ~CSTOPB;
}
break;
}
// 6.设置停止位
switch (stop_bits)
{
default:
case 1:
{
new_cfg.c_cflag &= ~CSTOPB;
}
break;
case 2:
{
new_cfg.c_cflag |= CSTOPB;
}
}
// 7.设置等待时间和最小接受字符
new_cfg.c_cc[VTIME] = 0;
new_cfg.c_cc[VMIN] = 1;
// 8.处理未接受字符
tcflush(fd, TCIFLUSH);
// 9.激活新配置
if ((tcsetattr(fd, TCSANOW, &new_cfg)) != 0)
{
perror("tcsetattr");
return -1;
}
return 0;
}
串口使用详解
在配置完串口的相关属性后,就可以对串口进行打开和读写操作了。它所使用的函数和普通文件的读写函数一样,都是 open()、write()和 read()。它们之间的区别的只是串口是一个终端设备,因此在选择函数的具体参数时会有一些区别。另外,这里会用到一些附加的函数,用于测试终端设备的连接情况等。下面将对其进行
具体讲解。