目录
4.2.6输入和输出波特率--speed_t c_ispeed 和 speed_t c_ospeed
4.4设置输入输出波特率--cfsetispeed、cfsetospeed
1.简介
该博客主要介绍在Linux操作系统下使用C语言编写打开串口和串口的初始化函数。
2.开发环境
操作系统:Ubuntu18.04
串口设备:USB转RS232线(PL2303GC)、DTU数传电台x2
3.打开串口编程
在LINUX操作系统下,一切皆是文件,串口设备也不例外。串口是在日常开发中使用频率非常高的外设,在此,介绍一下如何打开串口设备。
首先输入命令 ,查看一下有哪些串口设备。
cd /dev
ls -l
如下图所示的ttyS*和ttyUSB0就是串口设备:
我使用的是USB转RS232的设备,所以我的是ttyUSB0。
打开这个设备,和打开其他文件一样,使用open函数。代码如下:
#include <fcntl.h>
#include <termios.h>
int Serial_Init(const char *deviceName)
{
/* 1.打开设备 */
int fd = -1;
fd = open(deviceName, O_RDWR | O_NOCTTY | O_NONBLOCK);
if(fd < 0)
{
perror("Serial_Init:open");
return -1;
}
return fd;
}
这里使用O_RDWR | O_NOCTTY | O_NONBLOCK这三个属性,表示这个文件可读可写,告知进程该串口设备不成为控制终端,非阻塞模式。
如果不用O_NONBLOCK打开串口的话,有时候会打不开设备,程序在open时就阻塞住了。
4.串口初始化常用函数
串口初始化,主要就进行波特率,数据位,停止位,校验位,CTS/RTS等参数进行设置。下面先介绍常用的有关配置的函数。
4.1获取终端属性--tcgetattr
函数原型:
#include <termios.h>
#include <unistd.h>
int tcgetattr(int fd, struct termios *termios_p);
函数功能:用于获取终端设备属性的函数。
函数参数:1.fd:这是一个文件描述符,它指向要获取属性的终端设备。
2.termios_p:这是一个指向 struct termios 结构体的指针。struct termios 结构体用于存储终端设备的各种属性,tcgetattr 函数会将获取到的终端属性填充到该结构体中。
函数返回值:成功返回0,失败返回-1,并且设置错误号。
4.2结构体-- struct termios
对于本节,简单的过一下就好,不用深究,主要看4.2.3和4.2.6。
struct termios 是用于存储终端设备属性的结构体,它是终端控制(termios)函数族的重要组成部分,通过该结构体可以对终端设备的输入、输出、控制和本地模式等进行配置和管理。下面详细介绍该结构体的各个成员:
#include <termios.h>
struct termios {
tcflag_t c_iflag; /* 输入模式标志 */
tcflag_t c_oflag; /* 输出模式标志 */
tcflag_t c_cflag; /* 控制模式标志 */
tcflag_t c_lflag; /* 本地模式标志 */
cc_t c_cc[NCCS]; /* 控制字符数组 */
speed_t c_ispeed; /* 输入波特率 */
speed_t c_ospeed; /* 输出波特率 */
};
4.2.1输入模式标志--tcflag_t c_iflag
该成员用于控制输入数据的处理方式,常见的标志位及其含义如下表所示:
IGNBRK | 忽略输入中的 BREAK 条件(通常是一个特殊的信号,表示线路上的异常) |
BRKINT | 如果 IGNBRK 未设置,接收到 BREAK 条件时将产生一个中断信号 |
IGNPAR | 忽略输入中的奇偶校验错误 |
PARMRK | 如果 IGNPAR 未设置,在奇偶校验错误的字符前插入一个特殊的标记字符 |
INPCK | 启用输入奇偶校验 |
ISTRIP | 将输入字符截断为 7 位 |
INLCR | 将输入的换行符(\n)转换为回车符(\r) |
IGNCR | 忽略输入中的回车符(\r) |
ICRNL | 将输入的回车符(\r)转换为换行符(\n) |
IXON | 启用输出的 XON/XOFF 流控制(当输入缓冲区满时发送 XOFF 信号,缓冲区有空间时发送 XON 信号) |
IXOFF | 启用输入的 XON/XOFF 流控制 |
4.2.2输出模式标志--tcflag_t c_oflag
该成员用于控制输出数据的处理方式,常见的标志位及其含义如下:
OPOST | 启用输出处理,即对输出数据进行一些转换和处理 |
ONLCR | 如果 OPOST 已设置,将输出的换行符(\n)转换为回车符和换行符(\r\n) |
OCRNL | 如果 OPOST 已设置,将输出的回车符(\r)转换为换行符(\n) |
ONOCR | 如果 OPOST 已设置,在第 0 列不输出回车符 |
ONLRET | 如果 OPOST 已设置,换行符(\n)会自动执行回车操作 |
4.2.3控制模式标志--tcflag_t c_cflag
该成员用于控制终端设备的硬件特性和数据传输参数,常见的标志位及其含义如下:
CBAUD | 波特率掩码,用于选择波特率。B115200,B9600等等 |
CSIZE | 数据位掩码,用于选择数据位的位数(如 5、6、7 或 8 位)CS5,CS6,CS7,CS8 |
CSTOPB | 设置两个停止位(默认是一个停止位) |
CREAD | 启用接收器,使能数据接收 |
PARENB | 启用奇偶校验 |
PARODD | 使用奇校验(默认是偶校验) |
CLOCAL | 忽略调制解调器的控制信号,允许本地连接。 |
4.2.4本地模式标志--tcflag_t c_lflag
该成员用于控制终端设备的本地特性,如信号处理、回显等,常见的标志位及其含义如下:
ECHO | 启用字符回显,即输入的字符会在终端上显示出来 |
ECHOE | 如果 ECHO 已设置,在删除字符时会显示删除效果(如退格和空格) |
ECHOK | 如果 ECHO 已设置,在输入换行符或回车符时会回显删除字符 |
ECHONL | 如果 ECHO 未设置,仍然回显换行符 |
ICANON | 启用规范模式,即输入以行为单位进行处理,用户输入的字符会被缓存,直到按下换行符才会将整行数据发送给程序。 |
ISIG | 启用信号生成,即当用户输入特定的字符(如 Ctrl+C、Ctrl+Z)时会产生相应的信号 |
NOFLSH | 在产生信号时不刷新输入和输出缓冲区 |
4.2.5控制字符数组--cc_t c_cc[NCCS]
该数组用于存储一些特殊的控制字符,常见的控制字符及其索引如下:
VEOF | 文件结束符,通常是 Ctrl+D |
VEOL | 行结束符 |
VERASE | 删除字符,通常是 Backspace 或 Delete 键 |
VWERASE | 删除一个单词的字符 |
VKILL | 删除整行输入的字符 |
VINTR | 中断信号字符,通常是 Ctrl+C |
VQUIT | 退出信号字符,通常是 Ctrl+\ |
VSUSP | 挂起信号字符,通常是 Ctrl+Z |
4.2.6输入和输出波特率--speed_t c_ispeed 和 speed_t c_ospeed
c_ispeed :用于设置输入数据的波特率,即数据从终端设备传输到计算机的速度。
c_ospeed :用于设置输出数据的波特率,即数据从计算机传输到终端设备的速度。 常见的波特率值有 B9600、B115200 等。
4.3设置原始模式--cfmakeraw
cfmakeraw()用于将终端设备设置为原始模式(raw mode)的函数。在原始模式下,终端设备对输入和输出数据的处理变得非常简单,几乎不进行任何特殊的转换或处理,数据以最原始的形式进行传输。
函数原型:
#include <termios.h>
void cfmakeraw(struct termios *termios_p);
函数参数:termios_p:这是一个指向 struct termios 结构体的指针。struct termios 结构体用于存储终端设备的各种属性,cfmakeraw() 函数会修改该结构体中的成员,将终端设备的属性设置为原始模式。
对传递进来的结构体修改:
1.输入模式(c_iflag) :清除 IGNBRK、BRKINT、PARMRK、ISTRIP、INLCR、IGNCR、ICRNL 和 IXON 标志位。这意味着不会忽略 BREAK 条件、不处理奇偶校验错误、不进行换行符和回车符的转换,也不启用 XON/XOFF 流控制。
2.输出模式(c_oflag) :清除 OPOST 标志位,禁用输出处理,即不会对输出数据进行任何特殊的转换,如将换行符转换为回车符和换行符。
3.控制模式(c_cflag) :设置 CS8 标志位,将数据位设置为 8 位。清除 PARENB 标志位,禁用奇偶校验。清除 CSTOPB 标志位,设置一个停止位。设置 CREAD 标志位,启用接收器。
4.本地模式(c_lflag) :清除 ECHO、ECHONL、ICANON、ISIG 和 IEXTEN 标志位。这意味着不进行字符回显、不回显换行符、禁用规范模式(输入不以行为单位处理)、不生成信号,也不进行扩展输入处理。
5.控制字符(c_cc) :将 VMIN 设置为 1,表示最少需要读取一个字符。将 VTIME 设置为 0,表示不设置超时时间。
一般地,在使用串口读取传感器数据时,需要设置成原始模式!
4.4设置输入输出波特率--cfsetispeed、cfsetospeed
cfsetispeed函数和cfsetospeed函数分别设置输入输出波特率的函数。
函数原型:
#include <termios.h>
int cfsetispeed(struct termios *termios_p, speed_t speed);
int cfsetospeed(struct termios *termios_p, speed_t speed);
函数参数:
1.termios_p :指向 struct termios 结构体的指针。struct termios 结构体用于存储终端设备的各种属性,该函数会修改此结构体中的输入波特率设置。
2.speed :指定要设置的输入波特率,其类型为 speed_t。比如B9600,B115200等。
函数返回值:如果函数调用成功,返回值为 0。如果指定的波特率无效,返回值为 -1,并设置 errno 为 EINVAL。
4.5设置终端属性--tcsetattr
tcsetattr函数用于设置终端设备属性的函数,它属于终端控制(termios)函数族,和 tcgetattr 等函数配合使用,能实现对终端设备输入、输出、控制等多种属性的灵活配置。
函数原型:
#include <termios.h>
#include <unistd.h>
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
函数参数:
1.fd :文件描述符。传递对应终端设备的文件描述符。
2.optional_actions :用于指定属性变更的生效时间,有以下几种可选值:
TCSANOW | 立即将新的终端属性应用到指定的终端设备,当前正在进行的输入输出操作不受影响。 |
TCSADRAIN | 等待所有输出数据传输完成后,再将新的终端属性应用到指定的终端设备。适用于需要保证输出数据完整传输的场景。 |
TCSAFLUSH | 等待所有输出数据传输完成,并且丢弃当前输入缓冲区中的所有数据后,再应用新的终端属性。这可避免旧的输入数据干扰新属性下的操作。 |
3.termios_p :指向 struct termios 结构体的指针,该结构体包含了要设置的终端设备的属性。通常先通过 tcgetattr 获取当前属性,进行修改后再使用 tcsetattr 进行设置。
函数返回值:若函数调用成功,返回值为 0。若函数调用失败,返回值为 -1,并设置 errno 来指示具体的错误类型。
4.6清空串口缓冲区--tcflush
tcflush 是用于清空终端设备输入或输出缓冲区的函数,它是终端控制(termios)函数族的一部分,能够帮助开发者管理终端设备的数据流,确保数据的一致性和准确性。
函数原型:
#include <termios.h>
#include <unistd.h>
int tcflush(int fd, int queue_selector);
函数参数:
1.fd :这是一个文件描述符,代表要操作的终端设备。
2.queue_selector :用于指定要清空的缓冲区类型,有以下几种可选值:
TCIFLUSH | 清空输入缓冲区,即丢弃所有已经接收但尚未被程序读取的数据。 |
TCOFLUSH | 清空输出缓冲区,即丢弃所有已经准备好但尚未发送到终端设备的数据。 |
TCIOFLUSH | 同时清空输入缓冲区和输出缓冲区,既丢弃未读取的输入数据,也丢弃未发送的输出数据。 |
函数返回值:若函数调用成功,返回值为 0。若函数调用失败,返回值为 -1,并设置 errno 来指示具体的错误类型。
5.初始化代码示例
笔者一般使用串口连接传感器,进而进行发送数据读取数据。笔者使用的是DTU数传电台作为实验设备,使用USB转RS232线。电脑上插USB端,RS232插DTU数传电台上的接口。
将打开和串口初始化封装成一个函数,思路如下:
1.打开设备,设置可读可写,不成为终端设备,非阻塞模式
2.设置成原始模式
3.设置接收使能,设置停止位,数据位,校验位,波特率
4.设置属性
5.清空串口缓冲区
代码如下:
#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
/*******************************************************************
* 函数原型:int DTU_Init(const char *deviceName)
* 函数简介:打开DTU串口设备
* 函数参数:dtuDeviceName:串口设备字符串
* 函数返回值: 成功打开串口设备时,返回文件描述符,失败返回-1
*****************************************************************/
int DTU_Init(const char *deviceName)
{
/* 1.打开设备 */
int fd = -1;
fd = open(deviceName, O_RDWR | O_NOCTTY | O_NONBLOCK);
if(fd < 0)
{
perror("DTU_Init:open");
return -1;
}
/* 2.配置串口参数 - 设置为原始模式
设置8个数据位, 1个停止位, 无奇偶校验, 波特率115200 */
struct termios options = {0};
cfmakeraw(&options);
options.c_iflag &=~(CSIZE | CSTOPB | PARENB); //清除有关数据位的控制位,设置1个停止位,不使用校验位
options.c_cflag |= (CS8 | CREAD | CLOCAL); //设置数据位位8位,接收使能,忽略调制解调器控制信号
options.c_oflag &= ~OPOST ; //禁用输出处理
cfsetispeed(&options, B115200); //设置输入输出波特率为115200
cfsetospeed(&options, B115200);
options.c_cc[VMIN] = 1; //设置最少读取的字符数为1
options.c_cc[VTIME] = 10; //设置读取超时时间为 1 秒(单位为 0.1 秒)
/* 3.写入串口配置 */
int ret = -1;
ret = tcsetattr(fd, TCSANOW, &options);
if(ret < 0)
{
perror("DTU_Init:tcsetattr");
return -1;
}
/* 4.清空串口缓冲区 */
tcflush(fd, TCIOFLUSH);
usleep (10000) ;
return fd;
}