Linux 串口编程和程序相对来说是很简单的,之所以用博客连载来展示,主要是想在学会使用的基础上掌握相关背景,原理以及注意事项。相信在遇到问题的时候,我们就不会对于技术的概念和 API 的使用浅尝辄止了。下面进入具体应用案例,由于现在很多电脑已经没有引出串口以及波特率范围会受到限制,这里我以 CH340 USB 转串口芯片制作的模块为基础讲解串口应用程序开发,关于该芯片在 Linux 系统的使用以及驱动加载可以参考:CH340 Linux驱动使用教程。

设备的打开与关闭

1. int libtty_open(const char *devname);

函数功能:根据传入的串口设备名打开相应的设备。成功返回设备句柄,失败返回-1。

2. int libtty_close(int fd);

函数功能:关闭打开的设备句柄。成功返回0,失败返回负值。

设备的配置

1. int libtty_setopt(int fd, int speed, char databits, char stopbits, char parity);

函数功能:配置串口设备,传入参数依次为波特率设置、数据位设置、停止位设置、检验设置。

Notes:设备打开前,可以通过 ls /dev 确认自己的硬件设备名,对于 USB 转串口 IC,在系统下名称为 "ttyUSBx",设备序号是根据插入主机的先后顺序自动分配的,这里我的为 "ttyUSB0",读者根据自己的需要修改。

/*** libtty_open - open tty device* @devname: the device name to open** In this demo device is opened blocked, you could modify it at will.*/
int libtty_open(const char *devname)
{int fd = open(devname, O_RDWR | O_NOCTTY | O_NDELAY); int flags = 0;if (fd == -1) {                        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;}if (isatty(fd) == 0){printf("not tty device.\n");return -1;}elseprintf("tty device test ok.\n");return fd;
}

Note:

  • 传入的 devname 参数为设备绝对路径;
  • O_NOCTTY标志用于通知系统,这个程序不会成为对应这个设备的控制终端。如果没有指定这个标志,那么任何一个输入(如SIGINT等)都将会影响用户的进程;
  • O_NDELAY标志与O_NONBLOCK 等效,但这里不仅仅是设置为非阻塞,还用于通知系统,这个程序不关心 DCD 信号线所处的状态(即与设备相连的另一端是否激活或者停止)。如果用户指定了这一标志,则进程将会一直处在休眠状态,直到 DCD 信号线被激活;
  • 使用 fcntl 函数恢复设备状态为阻塞状态,在数据收发时就会进行等待;
  • 使用 isatty函数测试当前打开的设备句柄是否关联到终端设备,如果不是 tty 设备,那么也返回出错;
/*** 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 set** The function return 0 if success, or -1 if fail.*/
int libtty_setopt(int fd, int speed, int databits, int stopbits, char parity)
{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;/* set tty speed */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]);   } }/* set data bits */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; }/* set parity */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;default:  fprintf(stderr, "unsupported parity\n");return -1; } /* set stop bits */ switch (stopbits) {  case 1:   newtio.c_cflag &= ~CSTOPB; break;case 2:   newtio.c_cflag |= CSTOPB; break;default:   perror("unsupported stop bits\n"); return -1;}newtio.c_cc[VTIME] = 0;   /* 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;
}

Note:

  • 首先保存了原先串口配置,为了方便演示,这里保存为局部变量,实际使用时是需要把配置保存到全局 termios 结构体中的。使用tcgetattr还可以测试配置是否正确、串口是否可用等。返回值参见 man 手册;
  • 使用 CLOCAL 用于忽略所有 MODEM 状态信号线,CREAD 标志用于使能接收。CSIZE 为数据位掩码;
  • 调用 cfsetispeedcfsetospeed参数设置波特率,函数中引用了两个数组,在后面的完整代码中会看到,这样书写可以简化设置代码;
  • 通过控制 c_cflag 与 c_iflag 配置串口数据位、停止位以及校验设置等;
  • VTIMEVMIN作用已经讲解多次,不再赘述,值得注意的是,TIME 值的单位是十分之一秒;
  • 通过 tcflush清空输入和输出缓冲区,根据实际需要修改;
  • 最后通过 tcsetattr 函数对将配置实际作用于串口;
数据读写直接使用 readwrite 函数接口就可以了,因此没有列举出来。下面给出完整的串口读写测试代码:
/* TTY testing utility (using tty driver)* Copyright (c) 2017* This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License as published by* the Free Software Foundation; either version 2 of the License.** Cross-compile with cross-gcc -I /path/to/cross-kernel/include*/#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>  int speed_arr[] = {B115200,B57600,B38400,B19200,B9600,B4800,B2400,B1200,B300
};int name_arr[] = {115200,57600,38400,19200,9600,4800,2400,1200,300
};/*** 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 set** The function return 0 if success, or -1 if fail.*/
int libtty_setopt(int fd, int speed, int databits, int stopbits, char parity)
{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;/* set tty speed */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]);   } }/* set data bits */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; }/* set parity */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;default:  fprintf(stderr, "unsupported parity\n");return -1; } /* set stop bits */ switch (stopbits) {  case 1:   newtio.c_cflag &= ~CSTOPB; break;case 2:   newtio.c_cflag |= CSTOPB; break;default:   perror("unsupported stop bits\n"); return -1;}newtio.c_cc[VTIME] = 0;   /* 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;
}/*** libtty_open - open tty device* @devname: the device name to open** In this demo device is opened blocked, you could modify it at will.*/
int libtty_open(const char *devname)
{int fd = open(devname, O_RDWR | O_NOCTTY | O_NDELAY); int flags = 0;if (fd == -1) {                        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;}if (isatty(fd) == 0){printf("not tty device.\n");return -1;}elseprintf("tty device test ok.\n");return fd;
}/*** libtty_close - close tty device* @fd: the device handle**/
int libtty_close(int fd)
{return close(fd);
}void tty_test(int fd)
{int nwrite, nread;char buf[100];memset(buf, 0x32, sizeof(buf));while (1) {nwrite = write(fd, buf, sizeof(buf));printf("wrote %d bytes already.\n", nwrite);nread = read(fd, buf, sizeof(buf));printf("read %d bytes already.\n", nread);sleep(2);}}int main(int argc, char *argv[])
{int fd;int ret;fd = libtty_open("/dev/ttyUSB0");if (fd < 0) {printf("libtty_open error.\n");exit(0);}ret = libtty_setopt(fd, 9600, 8, 1, 'n');if (ret != 0) {printf("libtty_setopt error.\n");exit(0);}tty_test(fd);ret = libtty_close(fd);if (ret != 0) {printf("libtty_close error.\n");exit(0);}
}
执行成功的话,会在终端屏幕上看到每隔两秒输出串口成功发送和接收的字节数,测试时可以直接短接串口的发送和接收引脚进行测试。以下为成功测试截图:

关于 Linux 串口编程的其他文章,可以移步至以下链接:

  1. 《Linux 串口编程<一> 一些背景》
  2. 《Linux 串口编程<二> 深入了解 termios》
  3. 《Linux 串口编程<三> 使用termios与API 进行串口程序开发》
  4. 《Linux 串口编程<四> 串口设备程序开发》
有疑问的读者可以给我邮件或者评论,觉得对你有帮助就点赞吧~:-D

Linux 串口编程四 串口设备程序开发相关推荐

  1. 嵌入式Linux 串口编程系列4——EasyARM287开发板通过freemodbus实现Modbus通信

    前面的文章分析了串口的一些基本知识,在工业应用中,串口通信比较常用的协议就是Modbus RTU,freemodbus是一款微型modbus协议栈,之前对各种单片机.小型处理器支持的比较好,从V1.6 ...

  2. Linux 串口编程三 使用termios与API进行串口程序开发

    在 termios 结构体以及内部终端控制标志中,并非所有的参数对于实际的物理串口都是有效的,在使用过程中也不需要对于所有标志的作用都有所理解.事实上,快速掌握一项技术的核心点也是一种学习能力.对于使 ...

  3. Linux 串口编程二 深入了解 termios

    前言 这一系列串口编程重点在应用层编程,但是在讲解原理与相关概念时需要对驱动框架有个基础的认识.如果只是浅尝辄止,以后在遇到串口驱动与应用层程序调试难免遇到瓶颈.关于 tty驱动架构参见我的其他博客: ...

  4. Linux 串口编程一 一些背景

    在大部分讲解 Linux 编程书籍的时候会发现没有单独的串口编程章节,实际上串口编程已经被概括在了"终端"或者"终端IO"章节里面.在上一篇博客中对经常出现的几 ...

  5. Linux串口编程_termios

    1.1 Linux串口编程主要是设置structtermios结构体的个成员值.Termios是在POSIX规范中定义的标准接口,表示终端设备(包括虚拟终端丶串口等),串口是一种终端设备,一般通过终端 ...

  6. Linux串口编程(中断方式和select方式)

    Linux下的串口编程,在嵌入式开发中占据着重要的地位,因为很多的嵌入式设备都是通过串口交换数据的.在没有操作系统的我们可以使用UART的中断来出来数据的接受和发送,而在Linux操作系统下,我们也可 ...

  7. 嵌入式Linux 串口编程系列2--termios的VMIN和VTIME深入理解

    在上一篇文章中,我们介绍了串口的一些基本知识.串口配置接口 termios结构体的概念,串口的配置参数有n多个,这里面不用都背下来,什么时候使用,翻看手册即可,但是有两个 参数是一定要理解的,就是VM ...

  8. Linux串口编程详解

    Linux串口编程详解(阻塞模式.非阻塞模式.select函数) 之前一直觉得串口编程很简单,这两天仔细研究后发现串口里的各种参数还挺复杂,稍不注意就容易出错,这里总结一下网上的各种文章及自己的理解与 ...

  9. Linux串口编程 —— 发送的数据无法被接收,且被原封不动返回

    Linux串口编程--发送的数据无法被接收,且被原封不动返回 问题描述 使用 #include <fcntl.h> /*文件控制定义*/ #include <termios.h> ...

最新文章

  1. Redis集群方案之Twemproxy+HAProxy+Keepalived+Sentinel+主从复制(待实践)
  2. RABBITMQ 管理指南(添加虚拟HOST)
  3. python中用来回溯异常的模块_python中的异常处理使用说明
  4. 通过jQuery源码学习javascript(三)
  5. 《图像处理知识》宝藏总纲
  6. 软件工程网络15个人阅读作业1 (201521123107)
  7. 带你读AI论文丨用于目标检测的高斯检测框与ProbIoU
  8. android 饿了么地图,饿了么送餐位置地图定位代码
  9. 腾讯“云+未来”峰会亮相山城,助力重庆成为数字中国新标杆
  10. linux python-3.10.4 安装
  11. Ubuntu 配置 SFTP 服务器
  12. 转 计算广告 KPI 公式
  13. allshare cast安卓版下载_PanDownload 安卓手机版,解决百度网盘下载速度慢
  14. 张亚勤寄语哥伦比亚大学2020年毕业生:引领未知时代
  15. bzoj3668: [Noi2014]起床困难综合症
  16. 如何用算法预测世界杯?
  17. ipad协议更新非常稳定
  18. malloc失败的一个原因
  19. Not registered via @EnableConfigurationProperties, marked as Spring component, or scanned via @Confi
  20. 哪个软件能准确测试人脸,人脸识别软件哪个好?人脸识别软件推荐2020

热门文章

  1. leetcode1276. 不浪费原料的汉堡制作方案(贪心)
  2. React Native指南
  3. 小程序服务器域名5次_为什么您不应该在100美元的服务器上用5天的时间构建面向500,000个用户的应用程序...
  4. 学习深度学习需要哪些知识_您想了解的有关深度学习的所有知识
  5. Windows 应用容器化
  6. CSS3与页面布局学习笔记(六)——CSS3新特性(阴影、动画、渐变、变形( transform)、透明、伪元素等)...
  7. 20145228 《信息安全系统设计基础》第0周学习总结
  8. js实现同时提交多个表单
  9. 探讨LoadRunner的并发用户和集合点
  10. HubbleDotNet使用备忘