您当前的位置: 首页 >  linux

风间琉璃•

暂无认证

  • 0浏览

    0关注

    337博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Linux之串口应用

风间琉璃• 发布时间:2022-08-30 21:37:36 ,浏览量:0

目录

一、串口介绍

二、应用编程

1.struct termios 结构体

①输入模式:c_iflag

②输出模式: c_oflag

​ ③控制模式: c_cflag

 ④本地模式: c_lflag

 ⑤特殊控制字符: c_cc

 2.终端工作模式

 ①规范模式

②非规范模式

 ③原始模式(Raw mode)

三、串口编程步骤

1.打开串口设备文件

2.获取串口配置参数

 3.配置串口为原始模式

4.配置串口波特率

5.配置控制标志

6.配置帧结构

7.设置阻塞模式

8.写入配置 

9.读写数据

四、源代码 

一、串口介绍

串口全称叫做串行接口,串行接口指的是数据一个一个的按顺序传输,通信线路简单。使用两条线即可实现双向通信,一条用于发送,一条用于接收。串口(UART)在嵌入式 Linux 系统中常作为系统的标准输入、输出设备, 系统运行过程产生的打印信息通过串口输出;串口也作为系统的标准输入设备, 用户通过串口与 Linux 系统进行交互。所以串口在 Linux 系统就是一个终端。

终端 Terminal:   终端就是处理主机输入、输出的一套设备,它用来显示主机运算的输出,并且接受主机要求的输入。终端始终扮演着人机交互的角色,所谓 Terminal,即机器的边缘。  

终端分类:

①本地终端:对于 PC 机来说, PC 机连接了显示器、键盘以及鼠标等设备,一个显示器/键盘组合就是一个本地终端;

②用串口连接的远程终端:开发板通过串口线连接到一个带有显示器和键盘的 PC 机,在 PC 机通过运行一个终端模拟程序,如 Windows 超级终端、 putty、 MobaXterm、 SecureCRT 等来获取并显示开发板通过串口发出的数据、同样还可以通过这些终端模拟程序将用户数据通过串口发送给开发板 Linux 系统,系统接收到数据之后便会进行相应的处理。

③基于网络的远程终端:通过 ssh、 Telnet 这些协议登录到一个远程主机  

前两类称为物理终端,最后一个称为伪终端。前两类都是在本地就直接关联了物理设备的,如显示器、鼠标键盘、串口等之类的,这种终端叫做物理终端。第三类在本地则没有关联任何物理设备,注意,不要把物理网卡当成终端关联的物理设备,它们与终端并不直接相关,所以这类不直接关联物理设备的终端叫做伪终端。

设备节点:

①/dev/ttyX(X 是一个数字编号) ttyX(teletype)在 Linux 中, /dev/ttyX 代表的是上述提到的本地终端, 包括/dev/tty1~/dev/tty63 一共63 个本地终端, 也就是连接到本机的键盘显示器可以操作的终端。这是 Linux 内核在初始化时所生成的 63 个本地终端.

 ②/dev/pts/X(X 是一个数字编号)

 这类设备节点是伪终端对应的设备节点,伪终端对应的设备节点都在/dev/pts 目录下、以数字编号命令。通过ssh 或 Telnet 远程登录协议登录到开发板主机,开发板 Linux 系统会在/dev/pts 目录下生成一个设备节点,这个设备节点便对应伪终端。

 ③/dev/ttymxcX

串口终端设备节点/dev/ttymxcX,要注意的是, mxc 这个名字不是一定的,这个名字的命名与驱动有关系(与硬件平台有关) ,如果换一个硬件平台,那么它这个串口对应的设备节点就不一定是 mxcX 。

使用 who 命令来查看计算机系统当前连接了哪些终端,一个终端就表示有一个用户使用该计算机。

二、应用编程

 串口的应用编程是通过 ioctl()对串口进行配置,调用 read()读取串口的数据、调用 write()向串口写入数据。但是Linux 为上层用户做了一层封装,将这些 ioctl()操作封装成了一套标准的 API,直接使用这一套标准 API 编写自己的串口应用程序即可。

要使用 termios API,需要在应用程序中包含 termios.h 头文件

1.struct termios 结构体

该数据结构描述了终端的配置信息, 这些参数能够控制、影响终端的行为、特性,终端设备应用编程(串口应用编程) 主要就是对这个结构体进行配置。  

#include 

struct termios
{
    tcflag_t c_iflag;		/* 输入模式标志 */
    tcflag_t c_oflag;		/* 输出模式标志 */
    tcflag_t c_cflag;		/* 控制模式标志 */
    tcflag_t c_lflag;		/* 本地模式标志 */
    cc_t c_line;			/* 线路规程 */
    cc_t c_cc[NCCS];		/* 控制属性 */
    speed_t c_ispeed;		/* 输入速度 */
    speed_t c_ospeed;		/* 输出速度 */
#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
};

终端的参数按照不同模式分为如下几类: 输入模式、输出模式、控制模式、本地模式、线路规程、特殊控制字符、输入速率、输出速率。  

①输入模式:c_iflag

输入模式控制输入数据(终端驱动程序从串口或键盘接收到的字符数据)在被传递给应用程序之前的处理方式。通过设置 struct termios 结构体中 c_iflag 成员的标志对它们进行控制。

c_iflag成员宏:

②输出模式: c_oflag

输出模式控制输出字符的处理方式,即由应用程序发送出去的字符数据在传递到串口或屏幕之前是如何处理的。

 ③控制模式: c_cflag

控制模式控制终端设备的硬件特性,对于串口来说,可设置串口波特率、数据位、校验位、停止位等硬件特性。通过设置 struct termios 结构中 c_cflag 成员的标志对控制模式进行配置。

 在 struct termios 结构体中,有一个 c_ispeed 成员变量和 c_ospeed 成员变量,在其它一些系统中,可能会使用这两个变量来指定串口的波特率;在 Linux 系统下, 则是使用 CBAUD 位掩码所选择的几个 bit 位来指定串口波特率。

 ④本地模式: c_lflag

本地模式用于控制终端的本地数据处理和工作模式。 通过设置 struct termios 结构体中 c_lflag 成员的标志对本地模式进行配置。

 ⑤特殊控制字符: c_cc

特殊控制字符是一些字符组合,如 Ctrl+C、 Ctrl+Z 等, 当用户键入这样的组合键,终端会采取特殊处理方式。 struct termios 结构体中 c_cc 数组将各种特殊字符映射到对应的支持函数  

VMIN:在非规范模式下,指定最少读取的字符数 MIN; VTIME:非规范模式下, 指定读取的每个字符之间的超时时间(以分秒为单位) TIME。  

 2.终端工作模式

终端有三种工作模式,分别为规范模式(canonical mode)、非规范模式(non-canonical mode)和原始模式(raw mode)。通过在 struct termios 结构体的 c_lflag 成员中设置 ICANNON 标志来定义终端是以规范模式(设置 ICANNON 标志)还是以非规范模式(清除 ICANNON 标志)工作,默认情况为规范模式。

 ①规范模式

在规范模式下,所有的输入是基于行进行处理的。在用户输入一个行结束符(回车符、 EOF )之前,系统调用 read()函数是读不到用户输入的任何字符的。除了 EOF 之外的行结束符(回车符等)与普通字符一样会被 read()函数读取到缓冲区中。

在规范模式中,行编辑是可行的,而且一次 read()调用最多只能读取一行数据。如果在 read()函数中被请求读取的数据字节数小于当前行可读取的字节数,则 read()函数只会读取被请求的字节数,剩下的字节下次再被读取。

②非规范模式

在非规范模式下,所有的输入是即时有效的,不需要用户另外输入行结束符,而且不可进行行编辑。在非规范模式下,对参数 MIN(c_cc[VMIN])和 TIME(c_cc[VTIME])的设置决定 read()函数的调用方式。TIME 和 MIN 的值只能用于非规范模式,两者结合起来可以控制对输入数据的读取方式。 根据 TIME 和 MIN 的取值不同,会有以下 4 种不同情况:

1.MIN = 0 和 TIME = 0: 在这种情况下, read()调用总是会立即返回。若有可读数据,则读取数据并返回被读取的字节数; 否则读取不到任何数据并返回 0。

2.MIN > 0 和 TIME = 0:在这种情况下, read()函数会被阻塞, 直到有 MIN 个字符可以读取时才返回,返回值是读取的字符数量。到达文件尾时返回 0

3.MIN = 0 和 TIME > 0:在这种情况下, 只要有数据可读或者经过 TIME 个十分之一秒的时间, read()函数则立即返回,返回值为被读取的字节数。如果超时并且未读到数据,则 read()函数返回 0

4.MIN > 0 和 TIME > 0:在这种情况下, 当有 MIN 个字节可读或者两个输入字符之间的时间间隔超过 TIME 个十分之一秒时, read()函数才返回。因为在输入第一个字符后系统才会启动定时器,所以,在这种情况下, read()函数至少读取一个字节后才返回。  

 ③原始模式(Raw mode)

按照严格意义来讲,原始模式是一种特殊的非规范模式。在原始模式下,所有的输入数据以字节为单位被处理。在这个模式下,终端是不可回显的, 并且禁用终端输入和输出字符的所有特殊处理。 在程序中,可以通过调用 cfmakeraw()函数将终端设置为原始模式。  

三、串口编程步骤 1.打开串口设备文件
serial_fd = open("/dev/ttySAC0",O_RDWR|O_NOCTTY);
//O_NOCTTY -------- 打开的串口不作为控制终端

 调用 open()函数时,使用了O_NOCTTY 标志,该标志用于告知系统/dev/ttySAC0 它不会成为进程的控制终端。

2.获取串口配置参数

在配置串口之前, 会先获取到终端当前的配置参数,将其保存到一个 struct termios 结构体对象中,这样可以在之后很方便地将终端恢复到原来的状态。tcgetattr()函数可以获取到串口终端当前的配置参数, tcgetattr 函数原型如下所示:

#include 
#include 
int tcgetattr(int fd, struct termios *termios_p);

备份串口的配置参数:

struct termios old_cfg;
struct termios new_cfg;

tcgetattr(serial_fd, &old_cfg);
 3.配置串口为原始模式
new_cfg = old_cfg;

cfmakeraw(&new_cfg);
4.配置串口波特率
cfsetispeed(&new_cfg,B115200);
cfsetospeed(&new_cfg,B115200);

设置波特率有专门的函数,用户不能直接通过位掩码来操作。设置波特率的主要函数有 cfsetispeed()和cfsetospeed(),这两个函数在头文件中申明。cfsetispeed()函数设置数据输入波特率,而 cfsetospeed()函数设置数据输出波特率。还可以直接使用 cfsetspeed()函数一次性设置输入和输出波特率。

5.配置控制标志
new_cfg.c_cflag |= CREAD|CLOCAL;//使能数据接收和本地模式

使能接收功能只需在 struct termios 结构体的 c_cflag 成员中添加 CREAD 标志即可

6.配置帧结构
new_cfg.c_cflag &= ~CSTOPB;//1位停止位
new_cfg.c_cflag &= ~CSIZE;//去掉数据位屏蔽
new_cfg.c_cflag |= CS8;//8位数据位
new_cfg.c_cflag &= ~PARENB;//无校验

停止位则是通过设置 c_cflag 成员的 CSTOPB 标志而实现的。若停止位为一个比特, 则清除 CSTOPB 标志;若停止位为两个,则添加 CSTOPB 标志即可。

设置数据位大小无可用的函数, 需要通过位掩码来操作、设置数据位大小。 将 c_cflag 成员中 CSIZE 位掩码所选择的几个 bit 位清零,然后再设置数据位大小。

串口的奇偶校验位配置涉及到 struct termios 结构体中的两个成员变量: c_cflag 和 c_iflag。对于 c_cflag 成员,需要添加 PARENB 标志以使能串口的奇偶校验功能,只有使能奇偶校验功能之后才会对输出数据产生校验位,而对输入数据进行校验检查; 对于 c_iflag 成员来说,需要添加 INPCK 标志才能对接收到的数据执行奇偶校验。这里无检验。  

7.设置阻塞模式
tcflush(serial_fd,TCIOFLUSH);

//收到1字节解除阻塞
new_cfg.c_cc[VTIME] = 0;
new_cfg.c_cc[VMIN] = 1;

tcflush(serial_fd,TCIOFLUSH);

MIN 和 TIME 的取值会影响非规范模式下 read()调用的行为特征,原始模式是一种特殊的非规范模式,所以 MIN 和 TIME 在原始模式下也是有效的。

在对接收字符和等待时间没有特别要求的情况下,可以将 MIN 和 TIME 设置为 0, 这样则在任何情况下 read()调用都会立即返回,此时对串口的 read 操作会设置为非阻塞方式。这里将MIN设置为1,TIME设置为0,收到一个字节后解除阻塞。

在使用串口之前,需要对串口的缓冲区进行处理,因为在使用之前,其缓冲区中可能已经存在 一些数据等待处理或者当前正在进行数据传输、接收,可以调用中声明的 tcdrain()、 tcflow()、 tcflush()等函数来处理目前串口缓冲中的数据

#include 
#include 
int tcdrain(int fd);
int tcflush(int fd, int queue_selector);
int tcflow(int fd, int action);
//调用成功时返回 0;失败将返回-1、并且会设置 errno 以指示错误类型

调用 tcdrain()函数后会使得应用程序阻塞, 直到串口输出缓冲区中的数据全部发送完毕为止。

调用 tcflow()函数会暂停串口上的数据传输或接收工作,具体情况取决于参数 action

TCOOFF:暂停数据输出(输出传输); TCOON: 重新启动暂停的输出; TCIOFF: 发送 STOP 字符,停止终端设备向系统发送数据; TCION: 发送一个 START 字符,启动终端设备向系统发送数据;

调用tcflush()函数,调用该函数会清空输入/输出缓冲区中的数据,具体情况取决于参数queue_selector

TCIFLUSH: 对接收到而未被读取的数据进行清空处理; TCOFLUSH: 对尚未传输成功的输出数据进行清空处理; TCIOFLUSH: 包括前两种功能,即对尚未处理的输入/输出数据进行清空处理。

8.写入配置  
tcsetattr(serial_fd,TCSANOW,&new_cfg);
TCSANOW - 立即生效
TCSADRAIN - 输入输出完成后生效
TCSAFLUSH - 刷新缓冲区后生效

前面已经完成了对 struct termios 结构体各个成员进行配置,但是配置还未生效,需要将配置参数写入到串口,使其生效。通过 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: 所有已接收但未读取的输入都将在配置生效之前被丢弃。

9.读写数据

使用read/write发送和接收数据

四、源代码 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int serial_fd;
struct termios old_cfg;
struct termios new_cfg;


//调试串口初始化
void uart0_init()
{
	//1.打开串口设备文件
	serial_fd = open("/dev/ttySAC0",O_RDWR|O_NOCTTY);
	if(serial_fd==-1){
		perror("open");
		exit(-1);
	}
	
	//2.备份现有的串口配置
	if(-1==tcgetattr(serial_fd,&old_cfg)){
		perror("tcgetattr");
		exit(-1);
	}
	
	//3.原始模式
	new_cfg = old_cfg;
	cfmakeraw(&new_cfg);
	
	//4.配置波特率
	cfsetispeed(&new_cfg,B115200);
	cfsetospeed(&new_cfg,B115200);
	
	//5.设置控制标志
	new_cfg.c_cflag |= CREAD|CLOCAL;//使能数据接收和本地模式
	
	//6.设置帧结构
	new_cfg.c_cflag &= ~CSTOPB;//1位停止位
	new_cfg.c_cflag &= ~CSIZE;//去掉数据位屏蔽
	new_cfg.c_cflag |= CS8;//8位数据位
	new_cfg.c_cflag &= ~PARENB;//无校验	
	
	//7.设置阻塞模式
	tcflush(serial_fd,TCIOFLUSH);
	//收到1字节解除阻塞
	new_cfg.c_cc[VTIME] = 0;
	new_cfg.c_cc[VMIN] = 1;
	tcflush(serial_fd,TCIOFLUSH);
	
	//8.使能配置生效
	if(-1==tcsetattr(serial_fd,TCSANOW,&new_cfg)){
		perror("tcgetattr");
		exit(-1);
	}
}

int main()
{
	uart0_init();
	
	int i = 0;
	while(i            
关注
打赏
1665385461
查看更多评论
0.0377s