草庐IT

Linux串口编程

WCH-SoftGroup 2023-04-15 原文

在嵌入式应用领域中,串口是最为常见的一种硬件通信接口。因为其具备协议简单,硬件电路精简等优势使得串口基本成为MCU、计算机或嵌入式产品的标配接口。本文仅介绍在Linux系统下串口编程需要使用的API和一些应用技巧,关于串口的背景知识介绍,以及Windows系统下串口编程读者可以移步至其他文章。

Linux系统下串口的操作主要分为如下部分:

  • 串口打开、关闭
  • 串口参数设置
  • 串口数据发送与接收
  • 串口MODEM信号设置与读取
  • 串口Break信号发送

 

可以熟练掌握并应用以上串口功能已经可以应对Linux系统上串口应用的大多数场景了,针对更高级的串口用法可以阅读《Linux串口编程-进阶篇》,包含Linux系统使用非标准波特率、同步等待Modem信号变化、串口参数VTIME和VMIN的作用、RS485串口功能开关等。为方便用户使用我们将以上串口操作均封装成了独立的函数,可以极大的节约开发时间。

1、串口打开

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

 

Note:

  • devname 参数为设备绝对路径,如:“/dev/ttyUSB0”
  • O_NOCTTY标志用于通知系统,这个程序不会成为对应这个设备的控制终端。如果没有指定这个标志,那么任何一个输入(如SIGINT等)都将会影响用户的进程;
  • O_NDELAY标志与O_NONBLOCK 等效,但这里不仅仅是设置为非阻塞,还用于通知系统,这个程序不关心 DCD 信号线所处的状态(即与设备相连的另一端是否激活或者停止)。如果用户指定了这一标志,则进程将会一直处在休眠状态,直到 DCD 信号线被激活;

2、串口关闭

    /**
     * libtty_close - close tty device
     * @fd: the device handle
     *
     * The function return 0 if success, others if fail.
     */
    static int libtty_close(int fd)
    {
    	return close(fd);
    }

3、串口设置

    /**
     * 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 to set
     * @hardflow: hardflow to set
     *
     * The function return 0 if success, or -1 if fail.
     */
    static int libtty_setopt(int fd, int speed, int databits, int stopbits, char parity, char hardflow)
    {
    	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;
     
        /* 串口波特率设置*/
        switch (speed): {
        case 1200:
            cfsetspeed(&newtio, B1200);
            break;
        case 2400:
            cfsetspeed(&newtio, B2400);
            break;
        case 4800:
            cfsetspeed(&newtio, B4800);
            break;
        case 9600:
            cfsetspeed(&newtio, B9600);
            break;
        case 19200:
            cfsetspeed(&newtio, B19200);
            break;
        case 38400:
            cfsetspeed(&newtio, B38400);
            break;
        case 57600:
            cfsetspeed(&newtio, B57600);
            break;
        case 115200:
            cfsetspeed(&newtio, B115200);
            break;
        case 230400:
            cfsetspeed(&newtio, B230400);
            break;
        case 460800:
            cfsetspeed(&newtio, B460800);
            break;
        case 921600:
            cfsetspeed(&newtio, B921600);
            break;
        default:
            break;
        }
    	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]);   
    		} 
    	}
    	/* 数据位设置 */
    	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;
    	}
     
    	/* 校验位设置 */
    	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;
    	case 'm':
    	case 'M':
    		newtio.c_cflag |= PARENB;    /* Enable parity */
    		newtio.c_cflag |= CMSPAR;    /* Stick parity instead */
    		newtio.c_cflag |= PARODD;    /* Even parity instead of odd */
    		newtio.c_iflag |= INPCK;     /* Enable input parity check */
    		break;
    	case 's':
    	case 'S':
    		newtio.c_cflag |= PARENB;    /* Enable parity */
    		newtio.c_cflag |= CMSPAR;    /* Stick parity instead */
    		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;
    	}
     
    	/* 停止位设置 */
    	switch (stopbits) {
    	case 1:
    		newtio.c_cflag &= ~CSTOPB;
    		break;
    	case 2:
    		newtio.c_cflag |= CSTOPB;
    		break;
    	default:
    		perror("unsupported stop bits\n");
    		return -1;
    	}
     
        /* 硬件流控设置 */
    	if (hardflow)
    		newtio.c_cflag |= CRTSCTS;
    	else
    		newtio.c_cflag &= ~CRTSCTS;
     
        /* 串口读操作参数设置 */
    	newtio.c_cc[VTIME] = 10;	/* 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;
    }

常规串口参数的设置均可以通过如上函数进行设定,注释比较详细。

包括串口波特率、数据位、停止位、硬件流控设置等。

4、串口发送

    /**
     * libtty_write - write data to uart
     * @fd: file descriptor of tty device
     * @buf: buffer to write
     * @count: write length
     *
     * The function return the number of bytes written if success, others if fail.
     */
    static int libtty_write(int fd, char *buf, int count)
    {
        return write(fd, buf, count);
    }

5、串口读取

    /**
     * libtty_read - read data from uart
     * @fd: file descriptor of tty device
     * @buf: pointer to read buffer
     * count: read length
     *
     * The function return the number of bytes read if success, others if fail.
     */
    static int libtty_read(int fd, char *buf, int count)
    {
    	return read(fd, buf, count);
    }

6、串口MODEM设置

    /**
     * libtty_tiocmset - modem set
     * @fd: file descriptor of tty device
     * @bDTR: 0 on inactive, other on DTR active
     * @bRTS: 0 on inactive, other on RTS active
     *
     * The function return 0 if success, others if fail.
     */
    static int libtty_tiocmset(int fd, char bDTR, char bRTS)
    {
    	unsigned long controlbits = 0;
     
    	if (bDTR)
    		controlbits |= TIOCM_DTR;
    	if (bRTS)
    		controlbits |= TIOCM_RTS;
     
    	return ioctl(fd, TIOCMSET, &controlbits);
    }

MODEM输出信号包括DTR和RTS信号,这2个信号可以由串口应用进行控制,常用于下载或IO控制等。

针对TTL/CMOS串口,DTR和RTS无效时为高电平,有效时为高电平。

7、串口MODEM读取

    /**
     * libtty_tiocmget - modem get
     * @fd: file descriptor of tty device
     * @modembits: pointer to modem status
     *
     * The function return 0 if success, others if fail.
     */
    static int libtty_tiocmget(int fd, unsigned long *modembits)
    {
    	int ret;
     
    	ret = ioctl(fd, TIOCMGET, modembits);
    	if (ret == 0) {
    		if (*modembits & TIOCM_DSR)
    			printf("DSR Active!\n");
    		if (*modembits & TIOCM_CTS)
    			printf("CTS Active!\n");
    		if (*modembits & TIOCM_CD)
    			printf("DCD Active!\n");
    		if (*modembits & TIOCM_RI)
    			printf("RI Active!\n");
    	}
     
    	return ret;
    }

MODEM输出信号包括DSR、CTS、DCD和RI信号,这4个信号可以由串口应用主动读取其有效状态,常用于状态指示或同步等。

针对TTL/CMOS串口,DSR、CTS、DCD和RI无效时为高电平,有效时为高电平。

8、串口Break信号

    /**
     * libtty_sendbreak - uart send break
     * @fd: file descriptor of tty device
     *
     * Description:
     *  tcsendbreak() transmits a continuous stream of zero-valued bits for a specific duration, if the  termi©\
     *	nal is using asynchronous serial data transmission.  If duration is zero, it transmits zero-valued bits
     *	for at least 0.25 seconds, and not more that 0.5 seconds.  If duration is not zero, it sends  zero-val©\
     *	ued bits for some implementation-defined length of time.
     *
     *  If  the terminal is not using asynchronous serial data transmission, tcsendbreak() returns without tak©\
     *	ing any action.
     */
    static int libtty_sendbreak(int fd)
    {
    	return tcsendbreak(fd, 0);
    }

串口Break信号在一些特定场景下会使用到,针对TTL/CMOS串口而言,串口Break是指串口的TXD保持一定时间的低电平。常见于一些实验仪器需要使用Break信号作为有效的开始信号。需要注意:tcsendbreak的第二个参数填0,表示串口TXD持续低电平0.25s~0.5s,如果参数为非0值,则持续参数中指定的时间。

有关Linux串口编程的更多相关文章

  1. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  2. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  3. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  4. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  5. ruby - 如何以编程方式删除实例上的 "singleton information"以使其编码(marshal)? - 2

    我创建了一个由于“在运行时执行的单例元类定义”而无法编码的对象(这段代码的描述是否正确?)。这是通过以下代码执行的:#defineclassXthatmyusesingletonclassmetaprogrammingfeatures#throughcallofmethod:break_marshalling!classXdefbreak_marshalling!meta_class=class我该怎么做才能使对象编码正确?是否可以从对象instance_of_x的classX中“移除”单例组件?我真的需要一个建议,因为我们的一些对象需要通过Marshal.dump序列化机制进行缓存。

  6. Ruby 元编程问题 - 2

    我正在查看Ruby日志记录库Logging.logger方法并从sourceatgithub提出问题与这段代码有关:logger=::Logging::Logger.new(name)logger.add_appendersappenderlogger.additive=falseclass我知道类 最佳答案 这实际上删除了方法(当它实际被执行时)。这是确保close不会被调用两次的保障措施。看起来好像有嵌套的“class 关于Ruby元编程问题,我们在StackOverflow上找到一

  7. ruby - Paperclip:以编程方式分配图像并设置其名称 - 2

    使用Paperclip,我想从这样的URL抓取图像:require'open-uri'user.photo=open(url)问题是我最后得到一个像“open-uri20110915-4852-1o7k5uw”这样的文件名。有什么方法可以更改user.photo上的文件名?作为一个额外的变化,Paperclip将我的文件存储在S3上,所以如果我可以在初始分配中设置我想要的文件名就更好了,这样图像就会上传到正确的S3key。像这样:user.photo=open(url),:filename=>URI.parse(url).path 最佳答案

  8. ruby - 如何以编程方式检查证书是否已被吊销? - 2

    我正在开发一个xcode自动构建系统。在执行一些预构建验证时,我想检查指定的证书文件是否已被撤销。我了解securityverify-cert验证其他证书属性但不验证吊销。我如何检查撤销?我正在用Ruby编写构建系统,但我对任何语言的想法都持开放态度。我阅读了这个答案(Openssl-Howtocheckifacertificateisrevokedornot),但指向底部的链接(DoesOpenSSLautomaticallyhandleCRLs(CertificateRevocationLists)now?)进入的Material对我的目的来说有点过于复杂(用户上传已撤销的证书是一

  9. ruby - 如何保持我不常用的编程语言技能 - 2

    关闭。这个问题是off-topic.它目前不接受答案。想改进这个问题吗?Updatethequestion所以它是on-topic用于堆栈溢出。关闭11年前。Improvethisquestion我不经常使用ruby​​-通常它加起来相当于每两个月或更长时间编写一次脚本。我的大部分编程都是使用C++进行的,这与ruby​​有很大不同。由于我与ruby​​之间的差距如此之大,我总是忘记语言的基本方面(比如解析文本文件和其他简单的东西)。我想每天练习一些基本的东西,我想知道是否有一些我可以订阅的网站,并且会向我发送当天的Ruby问题或类似的东西。有人知道这样的站点/Internet服务吗?

  10. ruby - 如何以编程方式将 mp3 转换为 itunes 可播放的 aac/m4a 文件? - 2

    我一直在寻找一种以编程方式或通过命令行将mp3转换为aac的方法,但没有成功。理想情况下,我有一段代码可以从我的Rails应用程序中调用,将mp3转换为aac。我安装了ffmpeg和libfaac,并能够使用以下命令创建aac文件:ffmpeg-itest.mp3-acodeclibfaac-ab163840dest.aac当我将输出文件的名称更改为dest.m4a时,它无法在iTunes中播放。谢谢! 最佳答案 FFmpeg提供AAC编码功能(如果您已编译它们)。如果您使用的是Windows,则可以从here获取完整的二进制文件。

随机推荐