Linux 串口编程四 串口设备程序开发
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 为数据位掩码;
- 调用 cfsetispeed与cfsetospeed参数设置波特率,函数中引用了两个数组,在后面的完整代码中会看到,这样书写可以简化设置代码;
- 通过控制 c_cflag 与 c_iflag 配置串口数据位、停止位以及校验设置等;
- VTIME与VMIN作用已经讲解多次,不再赘述,值得注意的是,TIME 值的单位是十分之一秒;
- 通过 tcflush清空输入和输出缓冲区,根据实际需要修改;
- 最后通过 tcsetattr 函数对将配置实际作用于串口;
/* 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 串口编程的其他文章,可以移步至以下链接:
- 《Linux 串口编程<一> 一些背景》
- 《Linux 串口编程<二> 深入了解 termios》
- 《Linux 串口编程<三> 使用termios与API 进行串口程序开发》
- 《Linux 串口编程<四> 串口设备程序开发》
Linux 串口编程四 串口设备程序开发相关推荐
- 嵌入式Linux 串口编程系列4——EasyARM287开发板通过freemodbus实现Modbus通信
前面的文章分析了串口的一些基本知识,在工业应用中,串口通信比较常用的协议就是Modbus RTU,freemodbus是一款微型modbus协议栈,之前对各种单片机.小型处理器支持的比较好,从V1.6 ...
- Linux 串口编程三 使用termios与API进行串口程序开发
在 termios 结构体以及内部终端控制标志中,并非所有的参数对于实际的物理串口都是有效的,在使用过程中也不需要对于所有标志的作用都有所理解.事实上,快速掌握一项技术的核心点也是一种学习能力.对于使 ...
- Linux 串口编程二 深入了解 termios
前言 这一系列串口编程重点在应用层编程,但是在讲解原理与相关概念时需要对驱动框架有个基础的认识.如果只是浅尝辄止,以后在遇到串口驱动与应用层程序调试难免遇到瓶颈.关于 tty驱动架构参见我的其他博客: ...
- Linux 串口编程一 一些背景
在大部分讲解 Linux 编程书籍的时候会发现没有单独的串口编程章节,实际上串口编程已经被概括在了"终端"或者"终端IO"章节里面.在上一篇博客中对经常出现的几 ...
- Linux串口编程_termios
1.1 Linux串口编程主要是设置structtermios结构体的个成员值.Termios是在POSIX规范中定义的标准接口,表示终端设备(包括虚拟终端丶串口等),串口是一种终端设备,一般通过终端 ...
- Linux串口编程(中断方式和select方式)
Linux下的串口编程,在嵌入式开发中占据着重要的地位,因为很多的嵌入式设备都是通过串口交换数据的.在没有操作系统的我们可以使用UART的中断来出来数据的接受和发送,而在Linux操作系统下,我们也可 ...
- 嵌入式Linux 串口编程系列2--termios的VMIN和VTIME深入理解
在上一篇文章中,我们介绍了串口的一些基本知识.串口配置接口 termios结构体的概念,串口的配置参数有n多个,这里面不用都背下来,什么时候使用,翻看手册即可,但是有两个 参数是一定要理解的,就是VM ...
- Linux串口编程详解
Linux串口编程详解(阻塞模式.非阻塞模式.select函数) 之前一直觉得串口编程很简单,这两天仔细研究后发现串口里的各种参数还挺复杂,稍不注意就容易出错,这里总结一下网上的各种文章及自己的理解与 ...
- Linux串口编程 —— 发送的数据无法被接收,且被原封不动返回
Linux串口编程--发送的数据无法被接收,且被原封不动返回 问题描述 使用 #include <fcntl.h> /*文件控制定义*/ #include <termios.h> ...
最新文章
- Redis集群方案之Twemproxy+HAProxy+Keepalived+Sentinel+主从复制(待实践)
- RABBITMQ 管理指南(添加虚拟HOST)
- python中用来回溯异常的模块_python中的异常处理使用说明
- 通过jQuery源码学习javascript(三)
- 《图像处理知识》宝藏总纲
- 软件工程网络15个人阅读作业1 (201521123107)
- 带你读AI论文丨用于目标检测的高斯检测框与ProbIoU
- android 饿了么地图,饿了么送餐位置地图定位代码
- 腾讯“云+未来”峰会亮相山城,助力重庆成为数字中国新标杆
- linux python-3.10.4 安装
- Ubuntu 配置 SFTP 服务器
- 转 计算广告 KPI 公式
- allshare cast安卓版下载_PanDownload 安卓手机版,解决百度网盘下载速度慢
- 张亚勤寄语哥伦比亚大学2020年毕业生:引领未知时代
- bzoj3668: [Noi2014]起床困难综合症
- 如何用算法预测世界杯?
- ipad协议更新非常稳定
- malloc失败的一个原因
- Not registered via @EnableConfigurationProperties, marked as Spring component, or scanned via @Confi
- 哪个软件能准确测试人脸,人脸识别软件哪个好?人脸识别软件推荐2020
热门文章
- leetcode1276. 不浪费原料的汉堡制作方案(贪心)
- React Native指南
- 小程序服务器域名5次_为什么您不应该在100美元的服务器上用5天的时间构建面向500,000个用户的应用程序...
- 学习深度学习需要哪些知识_您想了解的有关深度学习的所有知识
- Windows 应用容器化
- CSS3与页面布局学习笔记(六)——CSS3新特性(阴影、动画、渐变、变形( transform)、透明、伪元素等)...
- 20145228 《信息安全系统设计基础》第0周学习总结
- js实现同时提交多个表单
- 探讨LoadRunner的并发用户和集合点
- HubbleDotNet使用备忘