
⚫ 串口终端设备节点/dev/ttymxcX:对于 ALPHA/Mini I.MX6U 开发板来说,有两个串口,也就是有两个串口终端,对应两个设备节点,如下所示:

struct termios 结构体 (输入设备是struct input_event 结构体)
对于终端来说,其应用编程内容无非包括两个方面的内容:配置和读写;对于配置来说,一个很重要的数据结构便是 struct termios 结构体,该数据结构描述了终端的配置信息,这些参数能够控制、影响终端的行为、特性,事实上,终端设备应用编程(串口应用编程)主要就是对这个结构体进行配置。
- 示例代码 26.1.1 struct termios 结构体
- 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 */
- };
⚫ 输入模式;⚫ 输出模式;⚫ 控制模式;⚫ 本地模式;⚫ 线路规程;⚫ 特殊控制字符;⚫ 输入速率;⚫ 输出速率。
一、输入模式:c_iflag
可用于 c_iflag 成员的宏如下所示:
- IGNBRK 忽略输入终止条件
- BRKINT 当检测到输入终止条件时发送 SIGINT 信号
- IGNPAR 忽略帧错误和奇偶校验错误
- PARMRK 对奇偶校验错误做出标记
- INPCK 对接收到的数据执行奇偶校验
- ISTRIP 将所有接收到的数据裁剪为 7 比特位、也就是去除第八位
- INLCR 将接收到的 NL(换行符)转换为 CR(回车符)
- IGNCR 忽略接收到的 CR(回车符)
- ICRNL 将接收到的 CR(回车符)转换为 NL(换行符)
- IUCLC 将接收到的大写字符映射为小写字符
- IXON 启动输出软件流控
- IXOFF 启动输入软件流控
-
- 以上所列举出的这些宏,我们可以通过 man 手册查询到它们的详细描述信息,执行命令" man 3 termios
- "
二、输出模式:c_oflag
- OPOST 启用输出处理功能,如果不设置该标志则其他标志都被忽略
- OLCUC 将输出字符中的大写字符转换成小写字符
- ONLCR 将输出中的换行符(NL '\n')转换成回车符(CR '\r')
- OCRNL 将输出中的回车符(CR '\r')转换成换行符(NL '\n')
- ONOCR 在第 0 列不输出回车符(CR)
- ONLRET 不输出回车符
- OFILL 发送填充字符以提供延时
- OFDEL 如果设置该标志,则表示填充字符为 DEL 字符,否则为 NULL 字符
- CBAUD 波特率的位掩码
- B0 波特率为 0
- …… ……
- B1200 1200 波特率
- B1800 1800 波特率
- B2400 2400 波特率
- B4800 4800 波特率
- B9600 9600 波特率
- B19200 19200 波特率
- B38400 38400 波特率
- B57600 57600 波特率
- B115200 115200 波特率
- B230400 230400 波特率
- B460800 460800 波特率
- B500000 500000 波特率
- B576000 576000 波特率
- B921600 921600 波特率
- B1000000 1000000 波特率
- B1152000 1152000 波特率
- B1500000 1500000 波特率
- B2000000 2000000 波特率
- B2500000 2500000 波特率
- B3000000 3000000 波特率
- …… ……
- CSIZE 数据位的位掩码
- CS5 5 个数据位
- CS6 6 个数据位
- CS7 7 个数据位
- CS8 8 个数据位
- CSTOPB 2 个停止位,如果不设置该标志则默认是一个停止位
- CREAD 接收使能
- PARENB 使能奇偶校验
- PARODD 使用奇校验、而不是偶校验
- HUPCL 关闭时挂断调制解调器
- CLOCAL 忽略调制解调器控制线
- CRTSCTS 使能硬件流控
- ISIG 若收到信号字符(INTR、QUIT 等),则会产生相应的信号
- ICANON 启用规范模式
- ECHO 启用输入字符的本地回显功能。当我们在终端输入字符的时候,字符
- 会显示出来,这就是回显功能
- ECHOE 若设置 ICANON,则允许退格操作
- ECHOK 若设置 ICANON,则 KILL 字符会删除当前行
- ECHONL 若设置 ICANON,则允许回显换行符
- ECHOCTL 若设置 ECHO,则控制字符(制表符、换行符等)会显示成“^X”
- ,
- 其中 X 的 ASCII 码等于给相应控制字符的 ASCII 码加上 0x40。例如,
- 退格字符(0x08)会显示为“^H”('H'的 ASCII 码为 0x48)
-
- ECHOPRT 若设置 ICANON 和 IECHO,则删除字符(退格符等)和被删除的字
- 符都会被显示
-
- ECHOKE 若设置 ICANON,则允许回显在 ECHOE 和 ECHOPRT 中设定的 KILL
- 字符
-
- NOFLSH 在通常情况下,当接收到 INTR、QUIT 和 SUSP 控制字符时,会清空
- 输入和输出队列。如果设置该标志,则所有的队列不会被清空
-
- TOSTOP 若一个后台进程试图向它的控制终端进行写操作,则系统向该后台进
- 程的进程组发送 SIGTTOU 信号。该信号通常终止进程的执行
-
- IEXTEN 启用输入处理功能
- ⚫ VEOF:文件结尾符 EOF,对应键为 Ctrl+D;该字符使终端驱动程序将输入行中的全部字符传递给
- 正在读取输入的应用程序。如果文件结尾符是该行的第一个字符,则用户程序中的 read 返回 0,表
- 示文件结束。
- ⚫ VEOL:附加行结尾符 EOL,对应键为 Carriage return(CR);作用类似于行结束符。
- ⚫ VEOL2:第二行结尾符 EOL2,对应键为 Line feed(LF);
- ⚫ VERASE:删除操作符 ERASE,对应键为 Backspace(BS);该字符使终端驱动程序删除输入行中
- 的最后一个字符;
- ⚫ VINTR:中断控制字符 INTR,对应键为 Ctrl+C;该字符使终端驱动程序向与终端相连的进程发送
- SIGINT 信号;
- ⚫ VKILL:删除行符 KILL,对应键为 Ctrl+U,该字符使终端驱动程序删除整个输入行;
- ⚫ VMIN:在非规范模式下,指定最少读取的字符数 MIN;
- ⚫ VQUIT:退出操作符 QUIT,对应键为 Ctrl+Z;该字符使终端驱动程序向与终端相连的进程发送
- SIGQUIT 信号。
- ⚫ VSTART:开始字符 START,对应键为 Ctrl+Q;重新启动被 STOP 暂停的输出。
- ⚫ VSTOP:停止字符 STOP,对应键为 Ctrl+S;字符作用“截流”,即阻止向终端的进一步输出。用
- 于支持 XON/XOFF 流控。
- ⚫ VSUSP:挂起字符 SUSP,对应键为 Ctrl+Z;该字符使终端驱动程序向与终端相连的进程发送
- SIGSUSP 信号,用于挂起当前应用程序。
- ⚫ VTIME:非规范模式下,指定读取的每个字符之间的超时时间(以分秒为单位)TIME。
ter.c_iflag |= (IGNBRK | BRKINT | PARMRK | ISTRIP);
规范模式
非规范模式下
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 fd;fd = open("/dev/ttymxc2", O_RDWR | O_NOCTTY);if (0 > fd) {perror("open error");return -1;}
#include #include int tcgetattr(int fd, struct termios *termios_p);需要包含 termios.h 头文件和 unistd.h 头文件。第一个参数对应串口终端设备的文件描述符 fd 。调用 tcgetattr 函数之前,我们需要定义一个 struct termios 结构体变量,将该变量的指针作为 tcgetattr() 函数的第二个参数传入函数调用成功返回 0 ;失败将返回 -1 ,并且会设置 errno 以告知错误原因。
- struct termios old_cfg;
- if (0 > tcgetattr(fd, &old_cfg)) {
- /* 出错处理 */
- do_something();
- }
struct termios new_cfg;memset(&new_cfg, 0x0, sizeof(struct termios)); //配置为原始模式cfmakeraw(&new_cfg);这个函数没有返回值。
new_cfg.c_cflag |= CREAD; //接收使能
cfsetispeed(&new_cfg, B115200);cfsetospeed(&new_cfg, B115200);B115200 是一个宏,前面已经给大家介绍了, B115200 表示波特率为 115200 。
cfsetspeed(&new_cfg, B115200);
new_cfg.c_cflag &= ~CSIZE;new_cfg.c_cflag |= CS8; //设置为 8 位数据位
// 奇校验使能new_cfg.c_cflag |= (PARODD | PARENB);new_cfg.c_iflag |= INPCK;// 偶校验使能new_cfg.c_cflag |= PARENB;new_cfg.c_cflag &= ~PARODD; /* 清除 PARODD 标志,配置为偶校验 */new_cfg.c_iflag |= INPCK;// 无校验new_cfg.c_cflag &= ~PARENB;new_cfg.c_iflag &= ~INPCK;
// 将停止位设置为一个比特new_cfg.c_cflag &= ~CSTOPB;// 将停止位设置为 2 个比特new_cfg.c_cflag |= CSTOPB;
new_cfg.c_cc[VTIME] = 0;new_cfg.c_cc[VMIN] = 0;
#include #include int tcdrain(int fd);int tcflush(int fd, int queue_selector);int tcflow(int fd, int action);
⚫ TCOOFF :暂停数据输出(输出传输);⚫ TCOON :重新启动暂停的输出;⚫ TCIOFF :发送 STOP 字符,停止终端设备向系统发送数据;⚫ TCION :发送一个 START 字符,启动终端设备向系统发送数据;
⚫ TCIFLUSH:对接收到而未被读取的数据进行清空处理;⚫ TCOFLUSH :对尚未传输成功的输出数据进行清空处理;⚫ TCIOFLUSH :包括前两种功能,即对尚未处理的输入 / 输出数据进行清空处理。以上这三个函数,调用成功时返回 0 ;失败将返回 -1 、并且会设置 errno 以指示错误类型
通常我们会选择 tcdrain()或 tcflush()函数来对串口缓冲区进行处理。譬如直接调用 tcdrain()阻塞:
tcdrain(fd);
或者调用 tcflush()清空缓冲区:
tcflush(fd, TCIOFLUSH);
操作五 写入配置、使配置生效:tcsetattr()函数
#include #include int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);调用该函数会将参数 termios_p 所指 struct termios 对象中的配置参数写入到终端设备中,使配置生效而参数 optional_actions 可以指定更改何时生效,其取值如下:⚫ TCSANOW :配置立即生效。⚫ TCSADRAIN :配置在所有写入 fd 的输出都传输完毕之后生效。⚫ TCSAFLUSH :所有已接收但未读取的输入都将在配置生效之前被丢弃。
操作六 读写数据:read()、write()
- #define _GNU_SOURCE //在源文件开头定义_GNU_SOURCE 宏
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- typedef struct uart_hardware_cfg {
- unsigned int baudrate; /* 波特率 */
- unsigned char dbit; /* 数据位 */
- char parity; /* 奇偶校验 */
- unsigned char sbit; /* 停止位 */
- } uart_cfg_t;
-
- static struct termios old_cfg; //用于保存终端的配置参数
- static int fd; //串口终端对应的文件描述符
-
- /**
- ** 串口初始化操作
- ** 参数 device 表示串口终端的设备节点
- **/
- static int uart_init(const char *device)
- {
- /* 打开串口终端 */
- fd = open(device, O_RDWR | O_NOCTTY);
- if (0 > fd) {
- fprintf(stderr, "open error: %s: %s\n", device, strerror(errno));
- return -1;
- }
- /* 获取串口当前的配置参数 备份数据*/
- if (0 > tcgetattr(fd, &old_cfg)) {
- fprintf(stderr, "tcgetattr error: %s\n", strerror(errno));
- close(fd);
- return -1;
- }
- return 0;
- }
- //
- /**
- ** 串口配置
- ** 参数 cfg 指向一个 uart_cfg_t 结构体对象
- **/
- static int uart_cfg(const uart_cfg_t *cfg)
- {
- struct termios new_cfg = {0}; //将 new_cfg 对象清零
- speed_t speed;
-
- /* 设置为原始模式 */
- cfmakeraw(&new_cfg);
-
- /* 使能接收 */
- new_cfg.c_cflag |= CREAD;
-
- /* 设置波特率 */
- switch (cfg->baudrate) {
- case 1200: speed = B1200;
- break;
- case 1800: speed = B1800;
- break;
- 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;
- case 57600: speed = B57600;
- break;
- case 115200: speed = B115200;
- break;
- case 230400: speed = B230400;
- break;
- case 460800: speed = B460800;
- break;
- case 500000: speed = B500000;
- break;
- default: //默认配置为 115200
- speed = B115200;
- printf("default baud rate: 115200\n");
- break;
- }
-
- if (0 > cfsetspeed(&new_cfg, speed)) {
- fprintf(stderr, "cfsetspeed error: %s\n", strerror(errno));
- return -1;
- }
-
- /* 设置数据位大小 */
- new_cfg.c_cflag &= ~CSIZE; //将数据位相关的比特位清零
- switch (cfg->dbit) {
- case 5:
- new_cfg.c_cflag |= CS5;
- break;
- case 6:
- new_cfg.c_cflag |= CS6;
- break;
- case 7:
- new_cfg.c_cflag |= CS7;
- break;
- case 8:
- new_cfg.c_cflag |= CS8;
- break;
- default: //默认数据位大小为 8
- new_cfg.c_cflag |= CS8;
- printf("default data bit size: 8\n");
- break;
- }
-
- /* 设置奇偶校验 */
- switch (cfg->parity) {
- case 'N': //无校验
- new_cfg.c_cflag &= ~PARENB;
- new_cfg.c_iflag &= ~INPCK;
- break;
- case 'O': //奇校验
- new_cfg.c_cflag |= (PARODD | PARENB);
- new_cfg.c_iflag |= INPCK;
- break;
- case 'E': //偶校验
- new_cfg.c_cflag |= PARENB;
- new_cfg.c_cflag &= ~PARODD; /* 清除 PARODD 标志,配置为偶校验 */
- new_cfg.c_iflag |= INPCK;
- break;
- default: //默认配置为无校验
- new_cfg.c_cflag &= ~PARENB;
- new_cfg.c_iflag &= ~INPCK;
- printf("default parity: N\n");
- break;
- }
-
- /* 设置停止位 */
- switch (cfg->sbit) {
- case 1: //1 个停止位
- new_cfg.c_cflag &= ~CSTOPB;
- break;
- case 2: //2 个停止位
- new_cfg.c_cflag |= CSTOPB;
- break;
- default: //默认配置为 1 个停止位
- new_cfg.c_cflag &= ~CSTOPB;
- printf("default stop bit size: 1\n");
- break;
- }
-
- /* 将 MIN 和 TIME 设置为 0 */
- new_cfg.c_cc[VTIME] = 0;
- new_cfg.c_cc[VMIN] = 0;
-
- /* 清空缓冲区 */
- if (0 > tcflush(fd, TCIOFLUSH)) {
- fprintf(stderr, "tcflush error: %s\n", strerror(errno));
- return -1;
- }
-
- /* 写入配置、使配置生效 */
- if (0 > tcsetattr(fd, TCSANOW, &new_cfg)) {
- fprintf(stderr, "tcsetattr error: %s\n", strerror(errno));
- return -1;
- }
-
- /* 配置 OK 退出 */
- return 0;
- }
-
- /***
- --dev=/dev/ttymxc2
- --brate=115200
- --dbit=8
- --parity=N
- --sbit=1
- --type=read
- ***/
- /**
- ** 终端打印帮助信息
- **/
- static void show_help(const char *app)
- {
- printf("Usage: %s [选项]\n"
- "\n 必选选项:\n"
- " --dev=DEVICE 指定串口终端设备名称, 譬如--dev=/dev/ttymxc2\n"
- " --type=TYPE 指定操作类型, 读串口还是写串口, 譬如--type=read(read 表示读、write 表示写、其它值无效)\n"
- "\n 可选选项:\n"
- " --brate=SPEED 指定串口波特率, 譬如--brate=115200\n"
- " --dbit=SIZE 指定串口数据位个数, 譬如--dbit=8(可取值为: 5/6/7/8)\n"
- " --parity=PARITY 指定串口奇偶校验方式, 譬如--parity=N(N 表示无校验、O 表示奇校验、E 表示偶校验)\n"
- " --sbit=SIZE 指定串口停止位个数, 譬如--sbit=1(可取值为: 1/2)\n"
- " --help 查看本程序使用帮助信息\n\n", app);
- }
-
- /**
- ** 信号处理函数,当串口有数据可读时,会跳转到该函数执行
- **/
- static void io_handler(int sig, siginfo_t *info, void *context)
- {
- unsigned char buf[10] = {0};
- int ret;
- int n;
- if(SIGRTMIN != sig)
- return;
- /* 判断串口是否有数据可读 */
- if (POLL_IN == info->si_code) {
- ret = read(fd, buf, 8); //一次最多读 8 个字节数据
- printf("[ ");
- for (n = 0; n < ret; n++)
- printf("0x%hhx ", buf[n]);
- printf("]\n");
- }
- }
-
- /**
- ** 异步 I/O 初始化函数
- **/
- static void async_io_init(void)
- {
-
- struct sigaction sigatn;
- int flag;
- /* 使能异步 I/O */
- flag = fcntl(fd, F_GETFL); //使能串口的异步 I/O 功能
- flag |= O_ASYNC;
- fcntl(fd, F_SETFL, flag);
- /* 设置异步 I/O 的所有者 */
- fcntl(fd, F_SETOWN, getpid());
- /* 指定实时信号 SIGRTMIN 作为异步 I/O 通知信号 */
- fcntl(fd, F_SETSIG, SIGRTMIN);
- /* 为实时信号 SIGRTMIN 注册信号处理函数 */
- sigatn.sa_sigaction = io_handler; //当串口有数据可读时,会跳转到 io_handler 函数
- sigatn.sa_flags = SA_SIGINFO;
- sigemptyset(&sigatn.sa_mask);
- sigaction(SIGRTMIN, &sigatn, NULL);
- }
- //
- //主函数
- int main(int argc, char *argv[])
- {
- uart_cfg_t cfg = {0};
- char *device = NULL;
- int rw_flag = -1;
- unsigned char w_buf[10] = {0x11, 0x22, 0x33, 0x44,0x55, 0x66, 0x77, 0x88};
- //通过串口发送出去的数据
- int n;
- /* 解析出参数 */
- for (n = 1; n < argc; n++) {
- if (!strncmp("--dev=", argv[n], 6))
- device = &argv[n][6];
- else if (!strncmp("--brate=", argv[n], 8))
- cfg.baudrate = atoi(&argv[n][8]);
- else if (!strncmp("--dbit=", argv[n], 7))
- cfg.dbit = atoi(&argv[n][7]);
- else if (!strncmp("--parity=", argv[n], 9))
- cfg.parity = argv[n][9];
- else if (!strncmp("--sbit=", argv[n], 7))
- cfg.sbit = atoi(&argv[n][7]);
- else if (!strncmp("--type=", argv[n], 7)) {
- if (!strcmp("read", &argv[n][7]))
- rw_flag = 0; //读
- else if (!strcmp("write", &argv[n][7]))
- rw_flag = 1; //写
- }
- else if (!strcmp("--help", argv[n])) {
- show_help(argv[0]); //打印帮助信息
- exit(EXIT_SUCCESS);
- }
- }
- if (NULL == device || -1 == rw_flag) {
- fprintf(stderr, "Error: the device and read|write type must be set!\n");
- show_help(argv[0]);
- exit(EXIT_FAILURE);
- }
-
- /* 串口初始化 */
- if (uart_init(device))
- exit(EXIT_FAILURE);
-
- /* 串口配置 */
- if (uart_cfg(&cfg)) {
- tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置
- close(fd);
- exit(EXIT_FAILURE);
- }
-
- /* 读|写串口 */
- switch (rw_flag) {
- case 0: //读串口数据
- async_io_init(); //我们使用异步 I/O 方式读取串口的数据,调用该函数去初始化串口的异步 I/O
- for ( ; ; )
- sleep(1); //进入休眠、等待有数据可读,有数据可读之后就会跳转到 io_handler()函数
- break;
- case 1: //向串口写入数据
- for ( ; ; ) { //循环向串口写入数据
- write(fd, w_buf, 8); //一次向串口写入 8 个字节
- sleep(1); //间隔 1 秒钟
- }
- break;
- }
-
- /* 退出 */
- tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置,这跟单片机不同
- close(fd);
- exit(EXIT_SUCCESS);
- }
执行测试程序后,测试程序会每隔 1 秒中将 8 个字节数据[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88] 写入到 RS232 串口,此时 PC 端串口调试助手便会接收到这些数据,如下所示:
